UNPKG

giveth-bridge

Version:

Mainnet -> sidechain Giveth Bridge.

459 lines (399 loc) 21.8 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _winston = require('winston'); var _winston2 = _interopRequireDefault(_winston); var _givethLiquidpledging = require('giveth-liquidpledging'); var _gasPrice = require('./gasPrice'); var _gasPrice2 = _interopRequireDefault(_gasPrice); var _utils = require('./utils'); var _ForeignGivethBridge = require('./ForeignGivethBridge'); var _ForeignGivethBridge2 = _interopRequireDefault(_ForeignGivethBridge); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var Verifier = function () { function Verifier(homeWeb3, foreignWeb3, nonceTracker, config, db) { _classCallCheck(this, Verifier); this.homeWeb3 = homeWeb3; this.foreignWeb3 = foreignWeb3; this.nonceTracker = nonceTracker; this.db = db; this.config = config; this.lp = new _givethLiquidpledging.LiquidPledging(foreignWeb3, config.liquidPledging); this.foreignBridge = new _ForeignGivethBridge2.default(foreignWeb3, config.foreignBridge); this.currentHomeBlockNumber = undefined; this.currentForeignBlockNumber = undefined; this.account = homeWeb3.eth.accounts.wallet[0]; } /* istanbul ignore next */ _createClass(Verifier, [{ key: 'start', value: function start() { var _this = this; var intervalId = setInterval(function () { return _this.verify(); }, this.config.pollTime); this.verify(); } }, { key: 'verify', value: function verify() { var _this2 = this; return Promise.all([this.homeWeb3.eth.getBlockNumber(), this.foreignWeb3.eth.getBlockNumber()]).then(function (_ref) { var _ref2 = _slicedToArray(_ref, 2), homeBlockNumber = _ref2[0], foreignBlockNumber = _ref2[1]; _this2.currentHomeBlockNumber = homeBlockNumber; _this2.currentForeignBlockNumber = foreignBlockNumber; return Promise.all([_this2.getFailedSendTxs(), _this2.getPendingTxs()]); }).then(function (_ref3) { var _ref4 = _slicedToArray(_ref3, 2), failedTxs = _ref4[0], pendingTxs = _ref4[1]; var failedPromises = failedTxs.map(function (tx) { return _this2.verifyTx(tx); }); var pendingPromises = pendingTxs.map(function (tx) { return _this2.verifyTx(tx); }); if (_this2.config.isTest) { return Promise.all([].concat(_toConsumableArray(failedPromises), _toConsumableArray(pendingPromises))); } }).catch(function (err) { return console.error('Failed to fetch block number ->', err); }); } }, { key: 'verifyTx', value: function verifyTx(tx) { var _this3 = this; var web3 = tx.toHomeBridge ? this.homeWeb3 : this.foreignWeb3; var currentBlock = tx.toHomeBridge ? this.currentHomeBlockNumber : this.currentForeignBlockNumber; var confirmations = tx.toHomeBridge ? this.config.homeConfirmations : this.foreignConfirmations; // order matters here var txHash = tx.reSendGiverTxHash || tx.reSendReceiverTxHash || tx.reSendCreateGiverTxHash || tx.txHash; if (tx.status === 'pending') { return web3.eth.getTransactionReceipt(txHash).then(function (receipt) { if (!receipt) return; // not mined // only update if we have enough confirmations if (currentBlock - receipt.blockNumber <= confirmations) return; if (receipt.status === true || receipt.status === '0x01' || receipt.status === '0x1' || receipt.status === 1) { // this was a createGiver tx, we still need to transfer the funds to the giver if (txHash === tx.reSendCreateGiverTxHash) { // GiverAdded event topic var _receipt$logs$find = receipt.logs.find(function (l) { return l.topics[0] === '0xad9c62a4382fd0ddbc4a0cf6c2bc7df75b0b8beb786ff59014f39daaea7f232f'; }), topics = _receipt$logs$find.topics; tx.giverId = _this3.homeWeb3.utils.hexToNumber(topics[1]); // idGiver is 1st indexed param, thus 2nd topic // we call handleFailedTx b/c this is still a failed tx. It is just multi-step b/c we needed to create a // giver. _winston2.default.debug('successfully created a giver ->', tx.giverId); return _this3.handleFailedTx(tx); } _this3.updateTxData(Object.assign(tx, { status: 'confirmed' })); return; } return _this3.handleFailedTx(tx); }).catch(function (err) { // ignore unknown tx b/c it is probably too early to check if (!err.message.includes('unknown transaction')) { _winston2.default.error('Failed to fetch tx receipt for tx', tx, err); } }); } else if (tx.status === 'failed-send') { return this.handleFailedTx(tx); } else { (0, _utils.sendEmail)(this.config, 'Unknown tx status \n\n ' + JSON.stringify(tx, null, 2)); _winston2.default.error('Unknown tx status ->', tx); } } }, { key: 'handleFailedTx', value: function handleFailedTx(tx) { var _this4 = this; var web3 = tx.toHomeBridge ? this.homeWeb3 : this.foreignWeb3; var handleFailedReceiver = function handleFailedReceiver() { return _this4.fetchAdmin(tx.receiverId).then(function (admin) { _winston2.default.debug('handling failed receiver ->', tx.receiverId, admin, tx); if (!admin || admin.adminType === '0') { // giver return _this4.sendToGiver(tx); } else if (admin.adminType === '1') { // delegate if (tx.reSendCreateGiver && !tx.reSendReceiver) { // giver failed, so try to send to receiver now return _this4.sendToReceiver(tx, tx.receiverId); } return _this4.sendToGiver(tx); } else if (admin.adminType === '2') { // project // check if there is a parentProject we can send to if project is canceled return _this4.getParentProjectNotCanceled(tx.receiverId).then(function (projectId) { if (!projectId || projectId === tx.receiverId && (!tx.reSendCreateGiver || tx.reSendReceiver) || projectId == 0) return _this4.sendToGiver(tx); return _this4.sendToReceiver(tx, projectId); }); } else { // shouldn't get here (0, _utils.sendEmail)(_this4.config, 'Unknown receiver adminType \n\n ' + JSON.stringify(tx, null, 2)); _winston2.default.error('Unknown receiver adminType ->', tx); } }); }; if (tx.toHomeBridge) { // this shouldn't fail, send email as we need to investigate (0, _utils.sendEmail)(this.config, 'AuthorizePayment tx failed toHomeBridge \n\n ' + JSON.stringify(tx, null, 2)); _winston2.default.error('AuthorizePayment tx failed toHomeBridge ->', tx); } else { // check that the giver is valid // if we don't have a giverId, we don't need to fetch the admin b/c this was a // donateAndCreateGiver call and we need to handle the failed receiver return (tx.giverId ? this.fetchAdmin(tx.giverId) : Promise.resolve(true)).then(function (giverAdmin) { return giverAdmin ? handleFailedReceiver() : _this4.createGiver(tx); }); } } }, { key: 'fetchAdmin', value: function fetchAdmin(id) { return this.lp.getPledgeAdmin(id).catch(function (e) { // receiver may not exist, catch error and pass undefined _winston2.default.debug('Failed to fetch pledgeAdmin for adminId ->', id, e); }); } }, { key: 'sendToGiver', value: function sendToGiver(tx) { var _this5 = this; _winston2.default.debug('send to Giver'); // already attempted to send to giver, notify of failure if (tx.reSendGiver) { this.updateTxData(Object.assign(tx, { status: 'failed' })); (0, _utils.sendEmail)(this.config, 'ForeignBridge sendToGiver Tx failed. NEED TO TAKE ACTION \n\n' + JSON.stringify(tx, null, 2)); _winston2.default.error('ForeignBridge sendToGiver Tx failed. NEED TO TAKE ACTION ->', tx); return; } if (!tx.giver && !tx.giverId) { (0, _utils.sendEmail)(this.config, 'Tx missing giver and giverId. Can\'t sendToGiver \n\n ' + JSON.stringify(tx, null, 2)); _winston2.default.error('Tx missing giver and giverId. Cant sendToGiver ->', tx); return; } if (tx.giver && !tx.giverId) return this.createGiver(tx); var data = this.lp.$contract.methods.donate(tx.giverId, tx.giverId, tx.sideToken, tx.amount).encodeABI(); var nonce = void 0; var txHash = void 0; return this.nonceTracker.obtainNonce().then(function (n) { nonce = n; return (0, _gasPrice2.default)(_this5.config, false); }).then(function (gasPrice) { return _this5.foreignBridge.bridge.deposit(tx.sender, tx.mainToken, tx.amount, tx.homeTx, data, { from: _this5.account.address, nonce: nonce, gasPrice: gasPrice, $extraGas: 100000 }).on('transactionHash', function (transactionHash) { _this5.nonceTracker.releaseNonce(nonce); _this5.updateTxData(Object.assign(tx, { status: 'pending', reSend: true, reSendGiver: true, reSendGiverTxHash: transactionHash })); txHash = transactionHash; }).catch(function (err, receipt) { _winston2.default.debug('ForeignBridge resend tx error ->', err, receipt, txHash); // if we have a txHash, then we will pick on the next run if (!txHash) { _this5.nonceTracker.releaseNonce(nonce, false, false); _this5.updateTxData(Object.assign(tx, { status: 'failed-send', reSend: true, reSendGiverTxHash: false, reSendGiver: true, reSendGiverError: err })); } }); }); } }, { key: 'createGiver', value: function createGiver(tx) { var _this6 = this; if (tx.reSendCreateGiver) { this.updateTxData(Object.assign(tx, { status: 'failed' })); (0, _utils.sendEmail)(this.config, 'ForeignBridge createGiver Tx failed. NEED TO TAKE ACTION \n\n' + JSON.stringify(tx, null, 2)); _winston2.default.error('ForeignBridge createGiver Tx failed. NEED TO TAKE ACTION ->', tx); return; } var nonce = void 0; var txHash = void 0; return this.nonceTracker.obtainNonce().then(function (n) { nonce = n; return (0, _gasPrice2.default)(_this6.config, false); }).then(function (gasPrice) { return _this6.lp.addGiver(tx.giver || tx.sender, '', '', 259200, 0, { from: _this6.account.address, nonce: nonce, gasPrice: gasPrice, $extraGas: 100000 }).on('transactionHash', function (transactionHash) { _this6.nonceTracker.releaseNonce(nonce); _this6.updateTxData(Object.assign(tx, { status: 'pending', reSend: true, reSendCreateGiver: true, reSendCreateGiverTxHash: transactionHash })); txHash = transactionHash; }).catch(function (err, receipt) { _winston2.default.debug('ForeignBridge resend createGiver tx error ->', err, receipt, txHash); // if we have a txHash, then we will pick on the next run if (!txHash) { _this6.nonceTracker.releaseNonce(nonce, false, false); _this6.updateTxData(Object.assign(tx, { status: 'failed-send', reSend: true, reSendCreateGiverError: err, reSendCreateGiverTxHash: false, reSendCreateGiver: true })); } }); }); } }, { key: 'sendToReceiver', value: function sendToReceiver(tx, newReceiverId) { var _this7 = this; if (tx.receiverId !== newReceiverId) { if (!tx.attemptedReceiverIds) tx.attemptedReceiverIds = [tx.receiverId]; tx.attemptedReceiverIds.push(newReceiverId); } tx.receiverId = newReceiverId; if (!tx.giver && !tx.giverId) { (0, _utils.sendEmail)(this.config, 'Tx missing giver and giverId. Can\'t sendToParentProject\n\n ' + JSON.stringify(tx, null, 2)); _winston2.default.error('Tx missing giver and giverId. Cant sendToParentProject ->', tx); return; } var data = void 0; if (tx.giver) { data = this.lp.$contract.methods.addGiverAndDonate(tx.receiverId, tx.giver, tx.sideToken, tx.amount).encodeABI(); } else { data = this.lp.$contract.methods.donate(tx.giverId, tx.receiverId, tx.sideToken, tx.amount).encodeABI(); } var nonce = void 0; var txHash = void 0; return this.nonceTracker.obtainNonce().then(function (n) { nonce = n; return (0, _gasPrice2.default)(_this7.config); }).then(function (gasPrice) { return _this7.foreignBridge.bridge.deposit(tx.sender, tx.mainToken, tx.amount, tx.homeTx, data, { from: _this7.account.address, nonce: nonce, gasPrice: gasPrice, $extraGas: 100000 }).on('transactionHash', function (transactionHash) { _this7.nonceTracker.releaseNonce(nonce); _this7.updateTxData(Object.assign(tx, { status: 'pending', reSend: true, reSendReceiver: true, reSendReceiverTxHash: transactionHash })); txHash = transactionHash; }).catch(function (err, receipt) { _winston2.default.debug('ForeignBridge resend tx error ->', err, receipt, txHash); // if we have a txHash, then we will pick on the next run if (!txHash) { _this7.nonceTracker.releaseNonce(nonce, false, false); _this7.updateTxData(Object.assign(tx, { status: 'failed-send', reSend: true, reSendReceiver: true, reSendReceiverTxHash: false, reSendReceiverError: err })); } }); }); } /** * if projectId is active, return projectId * otherwise returns first parentProject that is active * return undefined if no active project found * * @param {*} projectId * @returns Promise(projectId) */ }, { key: 'getParentProjectNotCanceled', value: function getParentProjectNotCanceled(projectId) { var _this8 = this; return this.lp.isProjectCanceled(projectId).then(function (isCanceled) { if (!isCanceled) return projectId; return _this8.lp.getPledgeAdmin(projectId).then(function (admin) { if (admin.parentProject) return _this8.getParentProjectNotCanceled(admin.parentProject); return undefined; }); }).catch(function (e) { _winston2.default.debug('Failed to getParentProjectNotCanceled =>', projectId); return undefined; }); } }, { key: 'updateTxData', value: function updateTxData(data) { var _id = data._id; this.db.txs.update({ _id: _id }, data, {}, function (err) { if (err) { _winston2.default.error('Error updating bridge-txs.db ->', err, data); process.exit(); } }); } }, { key: 'getFailedSendTxs', value: function getFailedSendTxs() { var _this9 = this; return new Promise(function (resolve, reject) { _this9.db.txs.find(_defineProperty({ status: 'failed-send', $or: [{ reSend: { $exists: false } }, { reSend: false }] }, '$or', [{ notified: { $exists: false } }, { notified: false }]), function (err, data) { if (err) { _winston2.default.error('Error fetching failed-send txs from db ->', err); resolve([]); return; } resolve(data); }); }); } }, { key: 'getPendingTxs', value: function getPendingTxs() { var _this10 = this; return new Promise(function (resolve, reject) { // this.db.txs.find({ status: 'pending' }, (err, data) => err ? reject(err) : resolve(Array.isArray(data) ? data : [data])) _this10.db.txs.find({ status: 'pending' }, function (err, data) { if (err) { _winston2.default.error('Error fetching pending txs from db ->', err); resolve([]); return; } resolve(data); }); }); } }]); return Verifier; }(); exports.default = Verifier;