From 037c797692936ffab3bbd49588f66f440ba18913 Mon Sep 17 00:00:00 2001 From: Scott Donald Date: Wed, 20 Jun 2018 17:38:44 +0200 Subject: [PATCH 01/10] Refactoring grpc connections --- imports/startup/both/nodes.js | 127 +++++--- imports/startup/client/functions.js | 42 ++- imports/startup/server/index.js | 287 ++++++++++++------ imports/ui/layouts/body/body.html | 2 +- imports/ui/layouts/body/body.js | 61 ++-- imports/ui/pages/tokens/tokenCreate.js | 13 +- imports/ui/pages/tokens/tokenCreateConfirm.js | 13 +- imports/ui/pages/tokens/tokenCreateResult.js | 9 +- imports/ui/pages/transfer/transfer.js | 32 +- imports/ui/pages/verify/tx.js | 16 +- mocknet.json | 14 +- testnet.json | 38 --- testslocal/001-wallet-loading.feature | 9 + testslocal/001-wallet-loading_steps.js | 3 + .../003-open-wallet-from-mnemonic.feature | 14 + .../003-open-wallet-from-mnemonic_steps.js | 26 ++ testslocal/004-create-wallet.feature | 14 + testslocal/004-create-wallet_steps.js | 5 + .../005-open-wallet-and-send-txn.feature | 29 ++ .../005-open-wallet-and-send-txn_steps.js | 3 + .../007-open-wallet-and-create-token.feature | 34 +++ .../007-open-wallet-and-create-token_steps.js | 112 +++++++ ...pen-wallet-and-send-token-transfer.feature | 27 ++ ...en-wallet-and-send-token-transfer_steps.js | 70 +++++ .../010-send-txn-from-shake256-wallet.feature | 26 ++ ...010-send-txn-from-shake256-wallet_steps.js | 3 + .../011-send-txn-from-sha2256-wallet.feature | 26 ++ .../011-send-txn-from-sha2256-wallet_steps.js | 3 + testslocal/features/helpers.js | 168 ++++++++++ 29 files changed, 954 insertions(+), 272 deletions(-) delete mode 100644 testnet.json create mode 100644 testslocal/001-wallet-loading.feature create mode 100644 testslocal/001-wallet-loading_steps.js create mode 100644 testslocal/003-open-wallet-from-mnemonic.feature create mode 100644 testslocal/003-open-wallet-from-mnemonic_steps.js create mode 100644 testslocal/004-create-wallet.feature create mode 100644 testslocal/004-create-wallet_steps.js create mode 100644 testslocal/005-open-wallet-and-send-txn.feature create mode 100644 testslocal/005-open-wallet-and-send-txn_steps.js create mode 100644 testslocal/007-open-wallet-and-create-token.feature create mode 100644 testslocal/007-open-wallet-and-create-token_steps.js create mode 100644 testslocal/009-open-wallet-and-send-token-transfer.feature create mode 100644 testslocal/009-open-wallet-and-send-token-transfer_steps.js create mode 100644 testslocal/010-send-txn-from-shake256-wallet.feature create mode 100644 testslocal/010-send-txn-from-shake256-wallet_steps.js create mode 100644 testslocal/011-send-txn-from-sha2256-wallet.feature create mode 100644 testslocal/011-send-txn-from-sha2256-wallet_steps.js create mode 100644 testslocal/features/helpers.js diff --git a/imports/startup/both/nodes.js b/imports/startup/both/nodes.js index 3d00af49..10d114bf 100644 --- a/imports/startup/both/nodes.js +++ b/imports/startup/both/nodes.js @@ -3,83 +3,126 @@ // All functions and variables are here are not defined by 'let' or 'const' // so that they can be utilised in other files within Meteor. -// Define the default nodes available in the UI. +// Define the default networks available in the UI. // eslint-disable-next-line no-unused-vars, no-undef -DEFAULT_NODES = [ +DEFAULT_NETWORKS = [ { - id: 'testnet-1', - name: 'Testnet (Official QRL Node 1)', + id: 'testnet', + name: 'Testnet', disabled: '', - explorerUrl: 'https://explorer.theqrl.org', - grpc: '104.237.3.185:9009', - type: 'both', - }, - { - id: 'testnet-2', - name: 'Testnet (Official QRL Node 2)', - disabled: '', - explorerUrl: 'https://explorer.theqrl.org', - grpc: '104.251.219.215:9009', - type: 'both', - }, - { - id: 'testnet-3', - name: 'Testnet (Official QRL Node 3)', - disabled: '', - explorerUrl: 'https://explorer.theqrl.org', - grpc: '104.251.219.145:9009', - type: 'both', - }, - { - id: 'testnet-4', - name: 'Testnet (Official QRL Node 4)', - disabled: '', - explorerUrl: 'https://explorer.theqrl.org', - grpc: '104.251.219.40:9009', + explorerUrl: 'https://testnet-explorer.theqrl.org', type: 'both', + healthy: false, + nodes: [ + { + id: 'testnet-1', + grpc: 'testnet-1.automated.theqrl.org:9009', + state: false, + height: 0 + }, + { + id: 'testnet-2', + grpc: 'testnet-2.automated.theqrl.org:9009', + state: false, + height: 0 + }, + { + id: 'testnet-3', + grpc: 'testnet-3.automated.theqrl.org:9009', + state: false, + height: 0 + }, + { + id: 'testnet-4', + grpc: 'testnet-4.automated.theqrl.org:9009', + state: false, + height: 0 + } + ] }, { id: 'mainnet', - name: 'Mainnet (Official QRL Node)', + name: 'Mainnet (QRL Foundation Nodes)', disabled: 'disabled', explorerUrl: 'https://explorer.theqrl.org', - grpc: '127.0.0.1:9009', type: 'both', + healthy: false, + nodes: [ + { + id: 'mainnet-1', + grpc: 'mainnet-1.automated.theqrl.org:9009', + state: false, + height: 0 + }, + { + id: 'mainnet-2', + grpc: 'mainnet-2.automated.theqrl.org:9009', + state: false, + height: 0 + }, + { + id: 'mainnet-3', + grpc: 'mainnet-3.automated.theqrl.org:9009', + state: false, + height: 0 + }, + { + id: 'mainnet-4', + grpc: 'mainnet-4.automated.theqrl.org:9009', + state: false, + height: 0 + } + ] }, { id: 'localhost', name: 'Localhost (Desktop App Only)', disabled: '', explorerUrl: 'http://explorer.theqrl.org', - grpc: 'localhost:9009', type: 'desktop', + healthy: false, + nodes: [ + { + id: 'localhost', + grpc: 'localhost:9009', + state: false, + height: 0 + } + ] } ] -// Override DEFAULT_NODES if provided in settings file +// Override DEFAULT_NETWORKS if provided in settings file try { - if (Meteor.settings.public.defaultNodes.length > 0) { - // Reset DEFAULT_NODES - DEFAULT_NODES = [] - // Set DEFAULT_NODES from Meteor settings - DEFAULT_NODES=Meteor.settings.public.defaultNodes + if (Meteor.settings.public.defaultNetworks.length > 0) { + // Reset DEFAULT_NETWORKS + DEFAULT_NETWORKS = [] + // Set DEFAULT_NETWORKS from Meteor settings + DEFAULT_NETWORKS=Meteor.settings.public.defaultNetworks } } catch (e) { // no configuration file used } -// Function to search through the DEFAULT_NODES array and identify and return an +// Function to search through the DEFAULT_NETWORKS array and identify and return an // object based on its 'id' value. // eslint-disable-next-line no-unused-vars, no-undef -findNodeData = (array, key) => { +findNetworkData = (array, key) => { if((LocalStore.get('nodeId') == 'custom') && (LocalStore.get('nodeStatus') != 'connecting')) { const nodeData = { id: 'custom', name: LocalStore.get('customNodeName'), disabled: '', explorerUrl: LocalStore.get('customNodeExplorerUrl'), - grpc: LocalStore.get('customNodeGrpc'), type: 'both', + nodes: [ + { + id: 'custom', + grpc: LocalStore.get('customNodeGrpc'), + state: false, + height: 0 + } + ] } return nodeData } else { diff --git a/imports/startup/client/functions.js b/imports/startup/client/functions.js index 1e2f9af8..139c6df3 100644 --- a/imports/startup/client/functions.js +++ b/imports/startup/client/functions.js @@ -12,9 +12,9 @@ isElectrified = () => { } // Returns the selected node -selectedNode = () => { - const selectedNode = document.getElementById('network').value - return selectedNode +selectedNetwork = () => { + const selectedNetwork = document.getElementById('network').value + return selectedNetwork } // Fetchs XMSS details from the global XMSS_OBJECT variable @@ -179,15 +179,31 @@ supportedBrowser = () => { } +// Wrapper for Meteor.call +wrapMeteorCall = (method, request, callback) => { + // Modify network to gRPC endpoint for custom/localhost settings + if (request.network == "localhost") { + // Override network to localhost + request.network = 'localhost:9009' + } + if (request.network == "custom") { + // Override network to localhost + request.network = LocalStore.get('nodeGrpc') + } + + Meteor.call(method, request, (err, res) => { + callback(err, res) + }) +} + // Get wallet address state details getBalance = (getAddress, callBack) => { - const grpcEndpoint = findNodeData(DEFAULT_NODES, selectedNode()).grpc const request = { address: addressForAPI(getAddress), - grpc: grpcEndpoint, + network: selectedNetwork() } - Meteor.call('getAddress', request, (err, res) => { + wrapMeteorCall('getAddress', request, (err, res) => { if (err) { console.log('err: ',err) LocalStore.set('transferFromBalance', 0) @@ -226,13 +242,13 @@ getBalance = (getAddress, callBack) => { loadAddressTransactions = (txArray) => { const request = { tx: txArray, - grpc: findNodeData(DEFAULT_NODES, selectedNode()).grpc, + network: selectedNetwork() } LocalStore.set('addressTransactions', []) $('#loadingTransactions').show() - Meteor.call('addressTransactions', request, (err, res) => { + wrapMeteorCall('addressTransactions', request, (err, res) => { if (err) { LocalStore.set('addressTransactions', { error: err }) } else { @@ -244,13 +260,12 @@ loadAddressTransactions = (txArray) => { } getTokenBalances = (getAddress, callback) => { - const grpcEndpoint = findNodeData(DEFAULT_NODES, selectedNode()).grpc const request = { address: addressForAPI(getAddress), - grpc: grpcEndpoint, + network: selectedNetwork() } - Meteor.call('getAddress', request, (err, res) => { + wrapMeteorCall('getAddress', request, (err, res) => { if (err) { console.log('err: ',err) LocalStore.set('transferFromBalance', 0) @@ -269,13 +284,12 @@ getTokenBalances = (getAddress, callback) => { let thisToken = {} - const grpcEndpoint = findNodeData(DEFAULT_NODES, selectedNode()).grpc const request = { query: tokenHash, - grpc: grpcEndpoint, + network: selectedNetwork() } - Meteor.call('getTxnHash', request, (err, res) => { + wrapMeteorCall('getTxnHash', request, (err, res) => { if (err) { console.log('err:',err) LocalStore.set('tokensHeld', []) diff --git a/imports/startup/server/index.js b/imports/startup/server/index.js index e25be1aa..b5f6d70e 100644 --- a/imports/startup/server/index.js +++ b/imports/startup/server/index.js @@ -36,29 +36,32 @@ const errorCallback = (error, message, alert) => { } // Load the qrl.proto gRPC client into qrlClient from a remote node. -const loadGrpcClient = (request, callback) => { +const loadGrpcClient = (endpoint, callback) => { // Load qrlbase.proto and fetch current qrl.proto from node const baseGrpcObject = grpc.load(Assets.absoluteFilePath('qrlbase.proto')) - const client = new baseGrpcObject.qrl.Base(request.grpc, grpc.credentials.createInsecure()) + const client = new baseGrpcObject.qrl.Base(endpoint, grpc.credentials.createInsecure()) client.getNodeInfo({}, (err, res) => { if (err) { - console.log(`Error fetching qrl.proto from ${request.grpc}`) + console.log(`Error fetching qrl.proto from ${endpoint}`) callback(err, null) } else { // Write a new temp file for this grpc connection const qrlProtoFilePath = tmp.fileSync({ mode: '0644', prefix: 'qrl-', postfix: '.proto' }).name fs.writeFile(qrlProtoFilePath, res.grpcProto, (fsErr) => { - if (fsErr) throw fsErr + if (fsErr) { + console.log(fsErr) + throw fsErr + } const grpcObject = grpc.load(qrlProtoFilePath) // Create the gRPC Connection - qrlClient[request.grpc] = - new grpcObject.qrl.PublicAPI(request.grpc, grpc.credentials.createInsecure()) + qrlClient[endpoint] = + new grpcObject.qrl.PublicAPI(endpoint, grpc.credentials.createInsecure()) - console.log(`qrlClient loaded for ${request.grpc}`) + console.log(`qrlClient loaded for ${endpoint}`) callback(null, true) }) @@ -66,36 +69,36 @@ const loadGrpcClient = (request, callback) => { }) } -// Client side function to establish a connection with a remote node. +// Establish a connection with a remote node. // If there is no active server side connection for the requested node, // this function will call loadGrpcClient to establish one. -const connectToNode = (request, callback) => { +const connectToNode = (endpoint, callback) => { // First check if there is an existing object to store the gRPC connection - if (qrlClient.hasOwnProperty(request.grpc) === true) { - console.log('Existing connection found for ', request.grpc, ' - attempting getNodeState') + if (qrlClient.hasOwnProperty(endpoint) === true) { + console.log('Existing connection found for ', endpoint, ' - attempting getNodeState') // There is already a gRPC object for this server stored. // Attempt to connect to it. try { - qrlClient[request.grpc].getNodeState({}, (err, response) => { + qrlClient[endpoint].getNodeState({}, (err, response) => { if (err) { - console.log('Error fetching node state for ', request.grpc) + console.log('Error fetching node state for ', endpoint) // If it errors, we're going to remove the object and attempt to connect again. - delete qrlClient[request.grpc] + delete qrlClient[endpoint] - console.log('Attempting re-connection to ', request.grpc) + console.log('Attempting re-connection to ', endpoint) - loadGrpcClient(request, (loadErr, loadResponse) => { + loadGrpcClient(endpoint, (loadErr, loadResponse) => { if (loadErr) { - console.log(`Failed to re-connect to node ${request.grpc}`) + console.log(`Failed to re-connect to node ${endpoint}`) const myError = errorCallback(err, 'Cannot connect to remote node', '**ERROR/connection** ') callback(myError, null) } else { - console.log(`Connected to ${request.grpc}`) + console.log(`Connected to ${endpoint}`) callback(null, loadResponse) } }) } else { - console.log(`Node state for ${request.grpc} ok`) + console.log(`Node state for ${endpoint} ok`) callback(null, response) } }) @@ -105,25 +108,140 @@ const connectToNode = (request, callback) => { callback(myError, null) } } else { - console.log(`Establishing new connection to ${request.grpc}`) + console.log(`Establishing new connection to ${endpoint}`) // We've not connected to this node before, let's establish a connection to it. - loadGrpcClient(request, (err, response) => { + loadGrpcClient(endpoint, (err) => { if (err) { - console.log(`Failed to connect to node ${request.grpc}`) + console.log(`Failed to connect to node ${endpoint}`) const myError = errorCallback(err, 'Cannot connect to remote node', '**ERROR/connection** ') callback(myError, null) } else { - console.log(`Connected to ${request.grpc}`) - callback(null, response) + console.log(`Connected to ${endpoint}`) + qrlClient[endpoint].getNodeState({}, (errState, response) => { + if (errState) { + console.log(`Failed to query node state ${endpoint}`) + const myError = errorCallback(err, 'Cannot connect to remote node', '**ERROR/connection** ') + callback(myError, null) + } else { + callback(null, response) + } + }) } }) } } +const checkNetworkHealth = (userNetwork, callback) => { + let networkHealthy = false + + // Determine current active nodes + DEFAULT_NETWORKS.forEach((network, networkIndex) => { + // Only look at health of userNetwork + if(network.id == userNetwork) { + if(network.healthy == true) { + networkHealthy = true + } + } + }) + + if (networkHealthy == true) { + callback(null, true) + } else { + callback(true, null) + } +} + +// Connect to all nodes +const connectNodes = () => { + // Establish gRPC connections with all enabled, non-localhost DEFAULT_NETWORKS + DEFAULT_NETWORKS.forEach((network, networkIndex) => { + if ((network.disabled === '') && (network.id !== 'localhost')) { + console.log(`Attempting to create gRPC connections to network: ${network.name} ...`) + + // Loop each node in the network and establish a gRPC connection. + const networkNodes = network.nodes + networkNodes.forEach((node, nodeIndex) => { + console.log(`Attempting to create gRPC connection to network: ${network.name}, node: ${node.id} (${node.grpc}) ...`) + const endpoint = node.grpc + connectToNode(endpoint, (err, res) => { + if (err) { + console.log(`Failed to connect to node ${endpoint}`) + DEFAULT_NETWORKS[networkIndex].nodes[nodeIndex].state = false + DEFAULT_NETWORKS[networkIndex].nodes[nodeIndex].height = 0 + } else { + console.log(`Connected to ${endpoint}`) + DEFAULT_NETWORKS[networkIndex].nodes[nodeIndex].state = true + DEFAULT_NETWORKS[networkIndex].nodes[nodeIndex].height = parseInt(res.info.block_height, 10) + // At least one node in the network is online, set network as healthy + DEFAULT_NETWORKS[networkIndex].healthy = true + } + }) + }) + } + }) +} + +// Wrapper to provide highly available API results in the event +// the primary or secondary nodes go offline +const qrlApi = (api, request, callback) => { + // Handle multi node network api requests + if((request.network == "devnet") || (request.network == "testnet") || (request.network == "mainnet")) { + // Store active nodes + const activeNodes = [] + + // Determine current active nodes + DEFAULT_NETWORKS.forEach((network, networkIndex) => { + // Only get nodes from user selected network + if(network.id == request.network) { + const networkNodes = network.nodes + networkNodes.forEach((node, nodeIndex) => { + if (node.state === true) { + activeNodes.push(node) + } + }) + } + }) + + // Determine node with highest block height and set as bestNode + const bestNode = {} + bestNode.grpc = '' + bestNode.height = 0 + activeNodes.forEach((node) => { + if (node.height > bestNode.height) { + bestNode.grpc = node.grpc + bestNode.height = node.height + } + }) + + // If all nodes are offline, fail + if (activeNodes.length === 0) { + const myError = errorCallback('The wallet server cannot connect to any API node', 'Cannot connect to API', '**ERROR/noActiveNodes/b**') + callback(myError, null) + } else { + // Make the API call + // Delete network from request object + delete request.network; + console.log('Making', api, 'request to', bestNode.grpc) + + qrlClient[bestNode.grpc][api](request, (error, response) => { + callback(error, response) + }) + } + } else { + // Handle custom and localhost connections + const apiEndpoint = request.network + // Delete network from request object + delete request.network; + qrlClient[apiEndpoint][api](request, (error, response) => { + callback(error, response) + }) + } +} + // Function to call getKnownPeers API. const getKnownPeers = (request, callback) => { - qrlClient[request.grpc].getKnownPeers({}, (err, response) => { + qrlApi('getKnownPeers', request, (err, response) => { if (err) { callback(err, null) } else { @@ -134,7 +252,7 @@ const getKnownPeers = (request, callback) => { const getStats = (request, callback) => { try { - qrlClient[request.grpc].getStats({}, (err, response) => { + qrlApi('getStats', request, (err, response) => { if (err) { const myError = errorCallback(err, 'Cannot access API/GetStats', '**ERROR/getStats** ') callback(myError, null) @@ -150,7 +268,7 @@ const getStats = (request, callback) => { // Function to call getAddressState API const getAddressState = (request, callback) => { - qrlClient[request.grpc].getAddressState({ address: request.address }, (err, response) => { + qrlApi('getAddressState', request, (err, response) => { if (err) { console.log(`Error: ${err.message}`) callback(err, null) @@ -228,7 +346,7 @@ const getTxnHash = (request, callback) => { const txnHash = Buffer.from(request.query, 'hex') try { - qrlClient[request.grpc].getObject({ query: txnHash }, (err, response) => { + qrlApi('getObject', { query: txnHash, network: request.network }, (err, response) => { if (err) { console.log(`Error: ${err.message}`) callback(err, null) @@ -248,10 +366,11 @@ const transferCoins = (request, callback) => { addresses_to: request.addresses_to, amounts: request.amounts, fee: request.fee, - xmss_pk: request.xmssPk + xmss_pk: request.xmssPk, + network: request.network } - qrlClient[request.grpc].transferCoins(tx, (err, response) => { + qrlApi('transferCoins', tx, (err, response) => { if (err) { console.log(`Error: ${err.message}`) callback(err, null) @@ -281,6 +400,7 @@ const confirmTransaction = (request, callback) => { // Overwrite addrs_to with our updated one confirmTxn.transaction_signed.transfer.addrs_to = addrs_to_Formatted + confirmTxn.network = request.network // Relay transaction through user node, then all default nodes. let txnResponse @@ -289,7 +409,7 @@ const confirmTransaction = (request, callback) => { // Relay through user node. function (wfcb) { try { - qrlClient[request.grpc].pushTransaction(confirmTxn, (err, res) => { + qrlApi('pushTransaction', confirmTxn, (err, res) => { console.log('Relayed Txn: ', Buffer.from(res.tx_hash).toString('hex')) if (err) { @@ -313,6 +433,7 @@ const confirmTransaction = (request, callback) => { wfcb() } }, + /* // Now relay through all default nodes that we have a connection too function(wfcb) { async.eachSeries(DEFAULT_NODES, (node, cb) => { @@ -342,6 +463,7 @@ const confirmTransaction = (request, callback) => { wfcb() }) }, + */ ], () => { // All done, send txn response txnResponse.relayed = relayedThrough @@ -364,9 +486,10 @@ const createTokenTxn = (request, callback) => { owner: request.owner, xmss_pk: request.xmssPk, xmss_ots_index: request.xmssOtsKey, + network: request.network } - qrlClient[request.grpc].getTokenTxn(tx, (err, response) => { + qrlApi('getTokenTxn', tx, (err, response) => { if (err) { console.log(`Error: ${err.message}`) callback(err, null) @@ -381,7 +504,6 @@ const createTokenTxn = (request, callback) => { }) } - const confirmTokenCreation = (request, callback) => { const confirmTxn = { transaction_signed: request.extended_transaction_unsigned.tx } const relayedThrough = [] @@ -408,6 +530,7 @@ const confirmTokenCreation = (request, callback) => { // Overwrite inital_balances with our updated one confirmTxn.transaction_signed.token.initial_balances = initialBalancesFormatted + confirmTxn.network = request.network // Relay transaction through user node, then all default nodes. let txnResponse @@ -416,7 +539,7 @@ const confirmTokenCreation = (request, callback) => { // Relay through user node. function (wfcb) { try{ - qrlClient[request.grpc].pushTransaction(confirmTxn, (err) => { + qrlApi('pushTransaction', confirmTxn, (err) => { if (err) { console.log(`Error: Failed to send transaction through ${request.grpc} - ${err}`) txnResponse = { error: err.message, response: err.message } @@ -438,6 +561,7 @@ const confirmTokenCreation = (request, callback) => { wfcb() } }, + /* // Now relay through all default nodes that we have a connection too function(wfcb) { async.eachSeries(DEFAULT_NODES, (node, cb) => { @@ -467,6 +591,7 @@ const confirmTokenCreation = (request, callback) => { wfcb() }) }, + */ ], () => { // All done, send txn response txnResponse.relayed = relayedThrough @@ -483,9 +608,10 @@ const createTokenTransferTxn = (request, callback) => { token_txhash: request.tokenHash, fee: request.fee, xmss_pk: request.xmssPk, + network: request.network } - qrlClient[request.grpc].getTransferTokenTxn(tx, (err, response) => { + qrlApi('getTransferTokenTxn', tx, (err, response) => { if (err) { console.log(`Error: ${err.message}`) callback(err, null) @@ -499,7 +625,6 @@ const createTokenTransferTxn = (request, callback) => { }) } - const confirmTokenTransfer = (request, callback) => { const confirmTxn = { transaction_signed: request.extended_transaction_unsigned.tx } const relayedThrough = [] @@ -521,6 +646,7 @@ const confirmTokenTransfer = (request, callback) => { // Overwrite addrs_to with our updated one confirmTxn.transaction_signed.transfer_token.addrs_to = addrs_to_Formatted + confirmTxn.network = request.network // Relay transaction through user node, then all default nodes. let txnResponse @@ -529,7 +655,7 @@ const confirmTokenTransfer = (request, callback) => { // Relay through user node. function (wfcb) { try { - qrlClient[request.grpc].pushTransaction(confirmTxn, (err) => { + qrlApi('pushTransaction', confirmTxn, (err) => { if (err) { console.log(`Error: Failed to send transaction through ${request.grpc} - ${err}`) txnResponse = { error: err.message, response: err.message } @@ -551,6 +677,7 @@ const confirmTokenTransfer = (request, callback) => { wfcb() } }, + /* // Now relay through all default nodes that we have a connection too function(wfcb) { async.eachSeries(DEFAULT_NODES, (node, cb) => { @@ -580,6 +707,7 @@ const confirmTokenTransfer = (request, callback) => { wfcb() }) }, + */ ], () => { // All done, send txn response txnResponse.relayed = relayedThrough @@ -602,17 +730,19 @@ const apiCall = (apiUrl, callback) => { Meteor.methods({ connectToNode(request) { this.unblock() - check(request, Object) + check(request, String) const response = Meteor.wrapAsync(connectToNode)(request) return response }, + checkNetworkHealth(request) { + this.unblock() + check(request, String) + const response = Meteor.wrapAsync(checkNetworkHealth)(request) + return response + }, status(request) { this.unblock() check(request, Object) - if (qrlClient[request.grpc] == null) { - console.log(`No active grpc connection available - connecting to: ${request.grpc}`) - Meteor.wrapAsync(connectToNode)(request) - } const response = Meteor.wrapAsync(getStats)(request) return response }, @@ -625,20 +755,12 @@ Meteor.methods({ getAddress(request) { this.unblock() check(request, Object) - if (qrlClient[request.grpc] == null) { - console.log(`No active grpc connection available - connecting to: ${request.grpc}`) - Meteor.wrapAsync(connectToNode)(request) - } const response = Meteor.wrapAsync(getAddressState)(request) return response }, getTxnHash(request) { this.unblock() check(request, Object) - if (qrlClient[request.grpc] == null) { - console.log(`No active grpc connection available - connecting to: ${request.grpc}`) - Meteor.wrapAsync(connectToNode)(request) - } const response = Meteor.wrapAsync(getTxnHash)(request) return response }, @@ -646,10 +768,7 @@ Meteor.methods({ txhash(request) { this.unblock() check(request, Object) - if (qrlClient[request.grpc] == null) { - console.log(`No active grpc connection available - connecting to: ${request.grpc}`) - Meteor.wrapAsync(connectToNode)(request) - } + // asynchronous call to API const response = Meteor.wrapAsync(getTxnHash)(request) // use explorer-helpers npm module to format the reponse @@ -659,9 +778,10 @@ Meteor.methods({ if (output.transaction.tx.transactionType === 'transfer_token') { // Request Token Decimals / Symbol const symbolRequest = { - query: Buffer.from(output.transaction.tx.transfer_token.token_txhash).toString('hex'), - grpc: request.grpc, - } + query: Buffer.from(output.transaction.tx.transfer_token.token_txhash).toString('hex'), + network: request.network + } + const thisSymbolResponse = Meteor.wrapAsync(getTxnHash)(symbolRequest) const thisSymbol = Buffer.from(thisSymbolResponse.transaction.tx.token.symbol).toString() const thisName = Buffer.from(thisSymbolResponse.transaction.tx.token.name).toString() @@ -712,19 +832,11 @@ Meteor.methods({ transferCoins(request) { this.unblock() check(request, Object) - if (qrlClient[request.grpc] == null) { - console.log(`No active grpc connection available - connecting to: ${request.grpc}`) - Meteor.wrapAsync(connectToNode)(request) - } const response = Meteor.wrapAsync(transferCoins)(request) return response }, addressTransactions(request) { check(request, Object) - if (qrlClient[request.grpc] == null) { - console.log(`No active grpc connection available - connecting to: ${request.grpc}`) - Meteor.wrapAsync(connectToNode)(request) - } const targets = request.tx @@ -732,7 +844,7 @@ Meteor.methods({ targets.forEach((arr) => { const thisRequest = { query: arr.txhash, - grpc: request.grpc, + network: request.network } try { @@ -911,50 +1023,30 @@ Meteor.methods({ confirmTransaction(request) { this.unblock() check(request, Object) - if (qrlClient[request.grpc] == null) { - console.log(`No active grpc connection available - connecting to: ${request.grpc}`) - Meteor.wrapAsync(connectToNode)(request) - } const response = Meteor.wrapAsync(confirmTransaction)(request) return response }, createTokenTxn(request) { this.unblock() check(request, Object) - if (qrlClient[request.grpc] == null) { - console.log(`No active grpc connection available - connecting to: ${request.grpc}`) - Meteor.wrapAsync(connectToNode)(request) - } const response = Meteor.wrapAsync(createTokenTxn)(request) return response }, confirmTokenCreation(request) { this.unblock() check(request, Object) - if (qrlClient[request.grpc] == null) { - console.log(`No active grpc connection available - connecting to: ${request.grpc}`) - Meteor.wrapAsync(connectToNode)(request) - } const response = Meteor.wrapAsync(confirmTokenCreation)(request) return response }, createTokenTransferTxn(request) { this.unblock() check(request, Object) - if (qrlClient[request.grpc] == null) { - console.log(`No active grpc connection available - connecting to: ${request.grpc}`) - Meteor.wrapAsync(connectToNode)(request) - } const response = Meteor.wrapAsync(createTokenTransferTxn)(request) return response }, confirmTokenTransfer(request) { this.unblock() check(request, Object) - if (qrlClient[request.grpc] == null) { - console.log(`No active grpc connection available - connecting to: ${request.grpc}`) - Meteor.wrapAsync(connectToNode)(request) - } const response = Meteor.wrapAsync(confirmTokenTransfer)(request) return response }, @@ -975,20 +1067,15 @@ if (Meteor.isServer) { Meteor.startup(() => { console.log(`QRL Wallet Starting - Version: ${WALLET_VERSION}`) - // Establish gRPC connections with all enabled, non-localhost DEFAULT_NODES - DEFAULT_NODES.forEach((node) => { - if ((node.disabled === '') && (node.id !== 'localhost')) { - console.log(`Attempting to create gRPC connection to node: ${node.name} (${node.grpc}) ...`) - - loadGrpcClient(node, (err) => { - if (err) { - console.log(`Error connecting to: ${node.name} (${node.grpc}) ...`) - } else { - console.log(`Connection created successfully for: ${node.name} (${node.grpc}) ...`) - } - }) - } - }) + // Attempt to create connections with all nodes + connectNodes() }) } +// Maintain node connection status +Meteor.setInterval(() => { + console.log('Refreshing node connection status') + + // Maintain state of connections to all nodes + connectNodes() +}, 20000) diff --git a/imports/ui/layouts/body/body.html b/imports/ui/layouts/body/body.html index 7fb08b0d..3119e2fa 100644 --- a/imports/ui/layouts/body/body.html +++ b/imports/ui/layouts/body/body.html @@ -89,7 +89,7 @@