UNPKG

scpx-wallet

Version:

Scoop Core Wallet: dual-signature timelock crypto wallet - multi-asset, cross-platform and open-source

363 lines (308 loc) 23.7 kB
// Distributed under AGPLv3 license: see /LICENSE for terms. Copyright 2019-2021 Dominic Morris. const CircularBuffer = require("circular-buffer") const isoWs = require('isomorphic-ws') const actionsWallet = require('../actions') const configWS = require('../config/websockets') const configWallet = require('../config/wallet') const configExternal = require('../config/wallet-external') const utilsWallet = require('../utils') module.exports = { getSyncInfo_Geth: (symbol, receivedBlockNo = undefined, receivedBlockTime = undefined, networkStatusChanged = undefined) => { return getSyncInfo_Geth(symbol, receivedBlockNo, receivedBlockTime, networkStatusChanged) }, isosocket_Disconnect_Geth: (networkConnected, networkStatusChanged, loaderWorker, walletSymbols) => { var disconnectCount = 0 for (var x in configWS.geth_ws_config) { if (self.geth_Sockets[x] !== undefined) { if (self.geth_Sockets[x].readyState == 0 || self.bb_Sockets[x].readyState == 1) { // connecting || open self.geth_Sockets[x].close() self.geth_Sockets[x] = undefined disconnectCount++ } if (!loaderWorker) { networkConnected(x, false) networkStatusChanged(x, {}) } } } return disconnectCount }, // geth tx and block subscriptions (diagnostics and balance polling, respectively) // considered VOLATILE -- no built-in reconnect isosocket_Setup_Geth: (networkConnected, networkStatusChanged, loaderWorker, walletSymbols) => { var setupCount = 0 //utilsWallet.debug(`appWorker >> ${self.workerId} geth_Setup...`) for (var assetSymbol in configWS.geth_ws_config) { // exclude if not in the loaded wallet if (walletSymbols && walletSymbols.length > 0) { if (!walletSymbols.includes(assetSymbol)) { utilsWallet.warn(`appWorker >> ${self.workerId} isosocket_Setup_Geth (skipping ${assetSymbol} - not in wallet)`, null, { logServerConsole: true }) continue } } if (assetSymbol === 'ETH_TEST') { if (!configWallet.WALLET_INCLUDE_ETH_TEST) continue } else if (!configWallet.getSupportedMetaKeyBySymbol(assetSymbol)) continue setupCount += (function (x) { // if we're called more than once, then the socket object already exists if (self.geth_Sockets[x] !== undefined) { // safari refocus handling // it may however be disconnected (e.g. when resuming from phone lock) // in this case, we detect it here and tear down the socket if (self.geth_Sockets[x].readyState == 2 || self.geth_Sockets[x].readyState == 3) { // if "closing" or "closed" respectively (connecting=0, open=1) utilsWallet.warn(`appWorker >> ${self.workerId} isosocket_Setup_Geth - ${x}: found disconnected socket for ${x} - nuking it!`) self.geth_Sockets[x].close() self.geth_Sockets[x] = undefined // rest of this fn. will now recreate the socket } } // initial / main path if (self.geth_Sockets[x] === undefined) { // connect & init // networkConnected(x, true) // init UI // networkStatusChanged(x, null) //utilsWallet.debug(`appWorker >> ${self.workerId} isosocket_Setup_Geth ${x}, wsUrl=`, configWS.geth_ws_config[x].url, { logServerConsole: true }) //debugger self.geth_Sockets[x] = new isoWs(configWS.geth_ws_config[x].url) //, { origin: 'https://x.scoop.tech' } var socket = self.geth_Sockets[x] // // socket lifecycle // socket.onopen = () => { utilsWallet.log(`appWorker >> ${self.workerId} isosocket_Setup_Geth ${x} - connect...`) try { if (!loaderWorker) { networkConnected(x, true) networkStatusChanged(x, { geth_url: configWS.geth_ws_config[x].url}) // subscribe new tx socket.send(`{"method":"eth_subscribe","params":["newPendingTransactions"],"id":1,"jsonrpc":"2.0"}`) // subscribe new blocks if (configWS.geth_ws_config[x].subBlocks === true) { socket.send(`{"method":"eth_subscribe","params":["newHeads"],"id":2,"jsonrpc":"2.0"}`) } // test - sub TUSD //socket.send(`{"method":"eth_subscribe","params":["logs", {"address": "0x0000000000085d4780b73119b644ae5ecd22b376"}],"id":3,"jsonrpc":"2.0"}`) } } catch (err) { utilsWallacket.error(`### appWorker >> ${self.workerId} isosocket_Setup_Geth ${x} - connect, err=`, err) } } socket.onclose = () => { utilsWallet.warn(`appWorker >> ${self.workerId} isosocket_Setup_Geth ${x} - onclose...`) self.geth_Sockets[x] = undefined // nuke this so volatileSockets_ReInit() triggers another setup try { if (!loaderWorker) { networkConnected(x, false) networkStatusChanged(x, { geth_url: configWS.geth_ws_config[x].url}) } } catch (err) { utilsWallet.error(`### appWorker >> ${self.workerId} isosocket_Setup_Geth ${x} - onclose callback, err=`, err) } } socket.onerror = (e) => { utilsWallet.warn(`appWorker >> ${self.workerId} isosocket_Setup_Geth ${x} - onerror...`) self.geth_Sockets[x] = undefined try { if (!loaderWorker) { networkConnected(x, false) networkStatusChanged(x, { geth_url: configWS.geth_ws_config[x].url }) } } catch (err) { utilsWallet.error(`### appWorker >> ${self.workerId} isosocket_Setup_Geth ${x} - onerror callback, err=`, err) } } // // subscriptions - new tx's and new blocks // if (!loaderWorker) { var tx_subId var block_subId //var tusd_subId socket.onmessage = (msg) => { if (msg && msg.data) { const o_data = JSON.parse(msg.data) if (o_data.id) { if (o_data.id == 1) { // tx sub ID tx_subId = o_data.result //utilsWallet.debug(`appWorker >> ${self.workerId} isosocket_Setup_Geth ${x} - tx sub setup, id=`, tx_subId) } else if (o_data.id == 2) { // block sub ID block_subId = o_data.result //utilsWallet.debug(`appWorker >> ${self.workerId} isosocket_Setup_Geth ${x} - block sub setup, id=`, block_subId) } // else if (o_data.id == 3) { // test sub TUSD // tusd_subId = o_data.result // utilsWallet.log(`appWorker >> ${self.workerId} isosocket_Setup_Geth ${x} - TUSD-test sub setup, id=`, tusd_subId) // } } else if (o_data.method && o_data.method === "eth_subscription" && o_data.params) { // if (o_data.params.subscription === tusd_subId) { // utilsWallet.log(`appWorker >> ${self.workerId} isosocket_Setup_Geth ${x} - TUSD-test sub DATA, o_data.params.result=`, o_data.params.result) // } if (o_data.params.subscription === tx_subId) { //console.log('o_data', o_data) //utilsWallet.log(`appWorker >> ${self.workerId} GETH WS ${x} - isoWS - TX`) // ### // // circular buffer for slightly better mempool tps est.? // // change footer ui so it can update tps OR last tx OR ... (not all at same time) // // add block TPS (actual) distinct from mempool tps (insight & geth) // const txid = o_data.params.result // if (!self.gethAllTxs[x]) self.gethAllTxs[x] = [] // if (self.gethAllTxs[x].includes(txid.toLowerCase())) { // console.warn(`dupe geth txid ${x}!`, txid) // } // else { // calc mempool tps (actually, the rate at which we're streaming from the mempool; // geth seems to want to give us all the current mempool tx's, not just new ones) // SKIP THIS - above, value just isn't useful // const BUF_CAP = 50 // var mempool_tps = 0 // if (!self.mempool_tpsBuf[x]) self.mempool_tpsBuf[x] = new CircularBuffer(BUF_CAP) // if (!self.mempool_tot[x]) self.mempool_tot[x] = 0 // if (!self.mempool_tpsBuf[x].toarray().some(p => p.txid == txid)) { // self.mempool_tpsBuf[x].push({ txid, timestamp: new Date().getTime() }) // self.mempool_tot[x]++ // } // if (mempool_tpsBuf[x].size() == BUF_CAP) { // const buf1 = mempool_tpsBuf[x].get(0) // const buf2 = mempool_tpsBuf[x].get(BUF_CAP - 1) // const ms = buf2.timestamp - buf1.timestamp // mempool_tps = BUF_CAP / (ms/1000) // } // throttle these to max n per sec //const sinceLastTx = new Date().getTime() - self.lastTx[x] //if (isNaN(sinceLastTx) || sinceLastTx > 500) { // self.lastTx[x] = new Date().getTime() // ## the rate calc'd above is the streaming rate of all txpool to client; it's not rate of newly // added to txpool - behaviour is confusing/different compared to insight mempool subscription // Jan '21 - don't call this at all for tx's (perf) // networkStatusChanged(x, { txid, // mempool_tps: -1, // N/A // geth_url: configWS.geth_ws_config[x].url // }) //} //} } else if (o_data.params.subscription === block_subId) { if (!configWallet.WALLET_DISABLE_BLOCK_UPDATES) { if (configWS.geth_ws_config[x].subBlocks === false) { //utilsWallet.debug(`appWorker >> ${self.workerId} GETH BLOCK WS ${x} - ignoring block: subBlocks=false`) } else { const blockData = o_data.params.result const receivedBlockNo = parseInt(blockData.number, 16) const receivedBlockTime = new Date(blockData.timestamp * 1000) if (!self.geth_BlockNos[x]) self.geth_BlockNos[x] = [] if (self.geth_BlockNos[x].some(p => p === receivedBlockNo)) { utilsWallet.warn(`appWorker >> ${self.workerId} GETH BLOCK WS ${x} - ${receivedBlockNo} ${receivedBlockTime} - ignoring, already seen this blockNo`) } else { self.geth_BlockNos[x].push(receivedBlockNo) utilsWallet.logMajor('blue','white', `appWorker >> ${self.workerId} GETH BLOCK WS ${x} - ${receivedBlockNo} ${receivedBlockTime}`) //, blockData) try { const dispatchActions = [] // save blockheight & time on asset eth[_test] asset // dispatchActions.push({ // type: actionsWallet.SET_ASSET_BLOCK_INFO, // payload: { symbol: x, receivedBlockNo, receivedBlockTime } // }) // // update lights - block tps // networkStatusChanged(x, { txid: undefined, mempool_tps: undefined, // block_no: receivedBlockNo }) getSyncInfo_Geth(x, receivedBlockNo, undefined, networkStatusChanged) // requery balance check for asset on new block - updates confirmed counts (this will trigger erc20 refresh from 3PBP as necessary) self.postMessage({ msg: 'REQUEST_STATE', status: 'REQ', data: { stateItem: 'ASSET', stateKey: x, context: 'ASSET_REFRESH_NEW_BLOCK' } }) // eth - same for all erc20s //if (x === 'ETH' || x === 'ETH_TEST') { const erc20_symbols = Object.keys(configExternal.erc20Contracts) erc20_symbols.forEach(erc20_symbol => { const meta = configWallet.getMetaBySymbol(erc20_symbol) //console.log(`GETH ${x} -> ${erc20_symbol} -> ${meta.isErc20_Ropsten}`) if ((x === 'ETH' && !meta.isErc20_Ropsten) || (x === 'ETH_TEST' && meta.isErc20_Ropsten)) { // save blockheight & time on asset erc20 asset dispatchActions.push({ type: actionsWallet.SET_ASSET_BLOCK_INFO, payload: { symbol: erc20_symbol, receivedBlockNo, receivedBlockTime } }) // // todo? (perf - but probably rapidly diminishing returns here) // change REQUEST_STATE to accept [] of asset for update in // a single batch via ASSET_REFRESH_NEW_BLOCK -> refreshAssetFull/refreshAssetBalance // (the latter two fn's would return [] of dispatchActions and caller (worker.js) would // send one batch of actions to update eth+[erc20's] in one hit) // self.postMessage({ msg: 'REQUEST_STATE', status: 'REQ', data: { stateItem: 'ASSET', stateKey: erc20_symbol, context: 'ASSET_REFRESH_NEW_BLOCK' } }) } }) //} // update batch self.postMessage({ msg: 'REQUEST_DISPATCH_BATCH', status: 'DISPATCH', data: { dispatchActions } }) } catch (err) { utilsWallet.error(`### appWorker >> ${self.workerId} GETH BLOCK ${x}, err=`, err) } } } } } } } } } return 1 } return 0 })(assetSymbol) } return setupCount } } async function getSyncInfo_Geth(symbol, _receivedBlockNo = undefined, _receivedBlockTime = undefined, networkStatusChanged = undefined) { if (symbol !== 'ETH' && symbol !== 'ETH_TEST') return if (!self.web3_Sockets[symbol] || self.web3_Sockets[symbol].currentProvider.connection.readyState != 1) { utilsWallet.warn(`appWorker >> ${self.workerId} getSyncInfo_Geth ${symbol} - ignoring: web3 WS not setup & ready for asset`) return } // get block - exact time & tx count const receivedBlockNo = _receivedBlockNo || (await self.web3_Sockets[symbol].eth.getBlockNumber()) const curBlock = await self.web3_Sockets[symbol].eth.getBlock(receivedBlockNo) const txCount = curBlock.transactions.length const receivedBlockTime = /*_receivedBlockTime ||*/ curBlock.timestamp self.postMessage({ msg: 'REQUEST_DISPATCH_BATCH', status: 'DISPATCH', data: { dispatchActions: [{ type: actionsWallet.SET_ASSET_BLOCK_INFO, payload: { symbol, receivedBlockNo, receivedBlockTime }} ] } }) if (!self.blocks_time[symbol]) self.blocks_time[symbol] = [] if (!self.blocks_tps[symbol]) self.blocks_tps[symbol] = [] if (!self.blocks_height[symbol]) self.blocks_height[symbol] = 0 // get prev block - exact time; for block TPS if (!self.blocks_time[symbol][receivedBlockNo - 1]) { const prevBlock = await self.web3_Sockets[symbol].eth.getBlock(receivedBlockNo - 1) self.blocks_time[symbol][receivedBlockNo - 1] = prevBlock.timestamp } const prevBlockTime = self.blocks_time[symbol][receivedBlockNo - 1] const block_time = receivedBlockTime - prevBlockTime const block_tps = block_time > 0 ? txCount / block_time : 0 if (self.blocks_height[symbol] < receivedBlockNo) { self.blocks_height[symbol] = receivedBlockNo self.blocks_tps[symbol].push(block_tps) } // console.log(`${symbol} blockData`, blockData) // console.log(`${symbol} txCount`, txCount) // console.log(`${symbol} receivedBlockTime`, receivedBlockTime) // update lights - block tps if (networkStatusChanged) { networkStatusChanged(symbol, { block_no: receivedBlockNo, block_txCount: txCount, block_tps: self.blocks_tps[symbol].reduce((a,b) => a + b, 0) / self.blocks_tps[symbol].length, block_count: self.blocks_tps[symbol].length, block_time, geth_url: configWS.geth_ws_config[symbol].url }) } }