diff --git a/config/config.json b/config/config.json index be35d05c56..5e2a9111a1 100644 --- a/config/config.json +++ b/config/config.json @@ -55,6 +55,7 @@ "network_id": "rinkeby", "gas_limit": "2000000", "gas_price": "20000000000", + "max_allowed_gas_price" : "50000000000", "hub_contract_address": "0x056f4DA796C00766061158A01cE068D912be9c89", "plugins": [ { @@ -91,7 +92,7 @@ "dc_holding_time_in_minutes": 60, "dc_token_amount_per_holder": "50000000000000000000", "dc_litigation_interval_in_minutes": 5, - "dh_max_holding_time_in_minutes": 10080, + "dh_max_holding_time_in_minutes": 2635200, "dh_min_token_price": "10000000000000000000", "dh_min_litigation_interval_in_minutes": 5, "deposit_on_demand": true, @@ -155,6 +156,7 @@ "network_id": "rinkeby", "gas_limit": "2000000", "gas_price": "20000000000", + "max_allowed_gas_price" : "50000000000", "hub_contract_address": "0x16d553B9765b6Aa9fE7C45Dfa1a1839fD88DD7bc", "plugins": [ { @@ -191,7 +193,7 @@ "dc_holding_time_in_minutes": 60, "dc_token_amount_per_holder": "1", "dc_litigation_interval_in_minutes": 5, - "dh_max_holding_time_in_minutes": 1440, + "dh_max_holding_time_in_minutes": 2635200, "dh_min_token_price": "1", "dh_min_litigation_interval_in_minutes": 5, "deposit_on_demand": false, @@ -255,6 +257,7 @@ "network_id": "rinkeby", "gas_limit": "2000000", "gas_price": "20000000000", + "max_allowed_gas_price" : "50000000000", "hub_contract_address": "0x6C314872A7e97A6F1dC7De2dc43D28500dd36f22", "plugins": [ { @@ -291,7 +294,7 @@ "dc_holding_time_in_minutes": 60, "dc_token_amount_per_holder": "50000000000000000000", "dc_litigation_interval_in_minutes": 5, - "dh_max_holding_time_in_minutes": 10080, + "dh_max_holding_time_in_minutes": 2635200, "dh_min_token_price": "10000000000000000000", "dh_min_litigation_interval_in_minutes": 5, "deposit_on_demand": false, @@ -355,6 +358,7 @@ "network_id": "rinkeby", "gas_limit": "2000000", "gas_price": "20000000000", + "max_allowed_gas_price" : "50000000000", "hub_contract_address": "0x0e5995FE34BfC9DB7a31fa318f7f1256Cf28FfCa", "plugins": [ { @@ -394,7 +398,7 @@ "dc_holding_time_in_minutes": 60, "dc_token_amount_per_holder": "50000000000000000000", "dc_litigation_interval_in_minutes": 5, - "dh_max_holding_time_in_minutes": 10080, + "dh_max_holding_time_in_minutes": 2635200, "dh_min_token_price": "10000000000000000000", "dh_min_litigation_interval_in_minutes": 5, "deposit_on_demand": false, @@ -458,6 +462,7 @@ "network_id": "mainnet", "gas_limit": "2000000", "gas_price": "20000000000", + "max_allowed_gas_price" : "50000000000", "hub_contract_address": "0xa287d7134fb40bef071c932286bd2cd01efccf30", "plugins": [ { @@ -497,7 +502,7 @@ "dc_holding_time_in_minutes": 10080, "dc_token_amount_per_holder": "50000000000000000000", "dc_litigation_interval_in_minutes": 5, - "dh_max_holding_time_in_minutes": 10080, + "dh_max_holding_time_in_minutes": 2635200, "dh_min_token_price": "20000000000000000000", "dh_min_litigation_interval_in_minutes": 5, "deposit_on_demand": false, diff --git a/migrations/201909241420156-offer-add-urgent.js b/migrations/201909241420156-offer-add-urgent.js new file mode 100644 index 0000000000..d1ec0ed475 --- /dev/null +++ b/migrations/201909241420156-offer-add-urgent.js @@ -0,0 +1,12 @@ + +module.exports = { + up: async (queryInterface, Sequelize) => + queryInterface.addColumn( + 'offers', + 'urgent', + { + type: Sequelize.BOOLEAN, + }, + ), + down: queryInterface => queryInterface.removeColumn('offers', 'urgent'), +}; diff --git a/models/offers.js b/models/offers.js index 2b56096388..767167226c 100644 --- a/models/offers.js +++ b/models/offers.js @@ -16,6 +16,7 @@ module.exports = (sequelize, DataTypes) => { blue_litigation_hash: DataTypes.STRING, green_litigation_hash: DataTypes.STRING, task: DataTypes.STRING, + urgent: DataTypes.BOOLEAN, status: DataTypes.STRING, message: DataTypes.STRING, transaction_hash: DataTypes.STRING(128), diff --git a/modules/Blockchain.js b/modules/Blockchain.js index 4ddd82bde2..6343072005 100644 --- a/modules/Blockchain.js +++ b/modules/Blockchain.js @@ -153,10 +153,11 @@ class Blockchain { * Pay out tokens * @param blockchainIdentity * @param offerId + * @param urgent * @returns {Promise} */ - payOut(blockchainIdentity, offerId) { - return this.blockchain.payOut(blockchainIdentity, offerId); + payOut(blockchainIdentity, offerId, urgent) { + return this.blockchain.payOut(blockchainIdentity, offerId, urgent); } /** @@ -175,6 +176,7 @@ class Blockchain { tokenAmountPerHolder, dataSizeInBytes, litigationIntervalInMinutes, + urgent, ) { return this.blockchain.createOffer( blockchainIdentity, @@ -188,6 +190,7 @@ class Blockchain { tokenAmountPerHolder, dataSizeInBytes, litigationIntervalInMinutes, + urgent, ); } @@ -204,10 +207,11 @@ class Blockchain { confirmation3, encryptionType, holders, + urgent, ) { return this.blockchain.finalizeOffer( blockchainIdentity, offerId, shift, confirmation1, - confirmation2, confirmation3, encryptionType, holders, + confirmation2, confirmation3, encryptionType, holders, urgent, ); } diff --git a/modules/Blockchain/Ethereum/index.js b/modules/Blockchain/Ethereum/index.js index f101ef26a0..54ec574d72 100644 --- a/modules/Blockchain/Ethereum/index.js +++ b/modules/Blockchain/Ethereum/index.js @@ -17,12 +17,14 @@ class Ethereum { web3, logger, appState, + gasPriceService, }) { // Loading Web3 this.appState = appState; this.emitter = emitter; this.web3 = web3; this.log = logger; + this.gasPriceService = gasPriceService; this.config = { wallet_address: config.node_wallet, @@ -258,16 +260,17 @@ class Ethereum { * @param blockchainIdentity - ERC 725 identity (empty if there is none) * @return {Promise} */ - createProfile( + async createProfile( managementWallet, profileNodeId, initialBalance, isSender725, blockchainIdentity, ) { + const gasPrice = await this.getGasPrice(); const options = { gasLimit: this.web3.utils.toHex(this.config.gas_limit), - gasPrice: this.web3.utils.toHex(this.config.gas_price), + gasPrice: this.web3.utils.toHex(gasPrice), to: this.profileContractAddress, }; this.log.trace(`CreateProfile(${managementWallet}, ${profileNodeId}, ${initialBalance}, ${isSender725}, ${blockchainIdentity})`); @@ -286,10 +289,11 @@ class Ethereum { * @param {number} tokenAmountIncrease * @returns {Promise} */ - increaseProfileApproval(tokenAmountIncrease) { + async increaseProfileApproval(tokenAmountIncrease) { + const gasPrice = await this.getGasPrice(); const options = { gasLimit: this.web3.utils.toHex(this.config.gas_limit), - gasPrice: this.web3.utils.toHex(this.config.gas_price), + gasPrice: this.web3.utils.toHex(gasPrice), to: this.tokenContractAddress, }; this.log.trace(`increaseProfileApproval(amount=${tokenAmountIncrease})`); @@ -302,10 +306,11 @@ class Ethereum { * @param amount * @return {Promise} */ - startTokenWithdrawal(blockchainIdentity, amount) { + async startTokenWithdrawal(blockchainIdentity, amount) { + const gasPrice = await this.getGasPrice(); const options = { gasLimit: this.web3.utils.toHex(this.config.gas_limit), - gasPrice: this.web3.utils.toHex(this.config.gas_price), + gasPrice: this.web3.utils.toHex(gasPrice), to: this.profileContractAddress, }; this.log.trace(`startTokenWithdrawal(blockchainIdentity=${blockchainIdentity}, amount=${amount}`); @@ -317,10 +322,11 @@ class Ethereum { * @param blockchainIdentity * @return {Promise} */ - withdrawTokens(blockchainIdentity) { + async withdrawTokens(blockchainIdentity) { + const gasPrice = await this.getGasPrice(); const options = { gasLimit: this.web3.utils.toHex(this.config.gas_limit), - gasPrice: this.web3.utils.toHex(this.config.gas_price), + gasPrice: this.web3.utils.toHex(gasPrice), to: this.profileContractAddress, }; this.log.trace(`withdrawTokens(blockchainIdentity=${blockchainIdentity}`); @@ -332,10 +338,11 @@ class Ethereum { * @param {number} tokenAmountIncrease * @returns {Promise} */ - increaseBiddingApproval(tokenAmountIncrease) { + async increaseBiddingApproval(tokenAmountIncrease) { + const gasPrice = await this.getGasPrice(); const options = { gasLimit: this.web3.utils.toHex(this.config.gas_limit), - gasPrice: this.web3.utils.toHex(this.config.gas_price), + gasPrice: this.web3.utils.toHex(gasPrice), to: this.tokenContractAddress, }; this.log.notify('Increasing bidding approval'); @@ -350,10 +357,11 @@ class Ethereum { * @param merkleProof * @return {Promise} */ - initiateLitigation(importId, dhWallet, blockId, merkleProof) { + async initiateLitigation(importId, dhWallet, blockId, merkleProof) { + const gasPrice = await this.getGasPrice(); const options = { gasLimit: this.web3.utils.toHex(this.config.gas_limit), - gasPrice: this.web3.utils.toHex(this.config.gas_price), + gasPrice: this.web3.utils.toHex(gasPrice), to: this.escrowContractAddress, }; this.log.important(`Initiates litigation for import ${importId} and DH ${dhWallet}`); @@ -376,10 +384,11 @@ class Ethereum { * @param requestedData * @return {Promise} */ - answerLitigation(importId, requestedData) { + async answerLitigation(importId, requestedData) { + const gasPrice = await this.getGasPrice(); const options = { gasLimit: this.web3.utils.toHex(this.config.gas_limit), - gasPrice: this.web3.utils.toHex(this.config.gas_price), + gasPrice: this.web3.utils.toHex(gasPrice), to: this.escrowContractAddress, }; this.log.important(`Answer litigation for import ${importId}`); @@ -401,10 +410,11 @@ class Ethereum { * @param proofData * @return {Promise} */ - proveLitigation(importId, dhWallet, proofData) { + async proveLitigation(importId, dhWallet, proofData) { + const gasPrice = await this.getGasPrice(); const options = { gasLimit: this.web3.utils.toHex(this.config.gas_limit), - gasPrice: this.web3.utils.toHex(this.config.gas_price), + gasPrice: this.web3.utils.toHex(gasPrice), to: this.escrowContractAddress, }; this.log.important(`Prove litigation for import ${importId} and DH ${dhWallet}`); @@ -424,12 +434,14 @@ class Ethereum { * Pay out tokens * @param blockchainIdentity * @param offerId + * @param urgent * @returns {Promise} */ - payOut(blockchainIdentity, offerId) { + async payOut(blockchainIdentity, offerId, urgent) { + const gasPrice = await this.getGasPrice(urgent); const options = { gasLimit: this.web3.utils.toHex(this.config.gas_limit), - gasPrice: this.web3.utils.toHex(this.config.gas_price), + gasPrice: this.web3.utils.toHex(gasPrice), to: this.holdingContractAddress, }; this.log.trace(`payOut(blockchainIdentity=${blockchainIdentity}, offerId=${offerId}`); @@ -440,7 +452,7 @@ class Ethereum { * Creates offer for the data storing on the Ethereum blockchain. * @returns {Promise} Return choose start-time. */ - createOffer( + async createOffer( blockchainIdentity, dataSetId, dataRootHash, @@ -452,10 +464,12 @@ class Ethereum { tokenAmountPerHolder, dataSizeInBytes, litigationIntervalInMinutes, + urgent, ) { + const gasPrice = await this.getGasPrice(urgent); const options = { gasLimit: this.web3.utils.toHex(this.config.gas_limit), - gasPrice: this.web3.utils.toHex(this.config.gas_price), + gasPrice: this.web3.utils.toHex(gasPrice), to: this.holdingContractAddress, }; this.log.trace(`createOffer (${blockchainIdentity}, ${dataSetId}, ${dataRootHash}, ${redLitigationHash}, ${greenLitigationHash}, ${blueLitigationHash}, ${dcNodeId}, ${holdingTimeInMinutes}, ${tokenAmountPerHolder}, ${dataSizeInBytes}, ${litigationIntervalInMinutes})`); @@ -482,7 +496,7 @@ class Ethereum { * Finalizes offer on Blockchain * @returns {Promise} */ - finalizeOffer( + async finalizeOffer( blockchainIdentity, offerId, shift, @@ -491,10 +505,12 @@ class Ethereum { confirmation3, encryptionType, holders, + urgent, ) { + const gasPrice = await this.getGasPrice(urgent); const options = { gasLimit: this.web3.utils.toHex(this.config.gas_limit), - gasPrice: this.web3.utils.toHex(this.config.gas_price), + gasPrice: this.web3.utils.toHex(gasPrice), to: this.holdingContractAddress, }; @@ -753,10 +769,11 @@ class Ethereum { * @param dhNodeId KADemlia ID of the DH node that wants to add bid * @returns {Promise} Index of the bid. */ - addBid(importId, dhNodeId) { + async addBid(importId, dhNodeId) { + const gasPrice = await this.getGasPrice(); const options = { gasLimit: this.web3.utils.toHex(this.config.gas_limit), - gasPrice: this.web3.utils.toHex(this.config.gas_price), + gasPrice: this.web3.utils.toHex(gasPrice), to: this.biddingContractAddress, }; @@ -792,9 +809,10 @@ class Ethereum { * @returns {Promise} */ async depositTokens(blockchainIdentity, amount) { + const gasPrice = await this.getGasPrice(); const options = { gasLimit: this.web3.utils.toHex(this.config.gas_limit), - gasPrice: this.web3.utils.toHex(this.config.gas_price), + gasPrice: this.web3.utils.toHex(gasPrice), to: this.profileContractAddress, }; @@ -826,10 +844,11 @@ class Ethereum { return this.readingContract.methods.purchased_data(importId, wallet).call(); } - initiatePurchase(importId, dhWallet, tokenAmount, stakeFactor) { + async initiatePurchase(importId, dhWallet, tokenAmount, stakeFactor) { + const gasPrice = await this.getGasPrice(); const options = { gasLimit: this.web3.utils.toHex(this.config.gas_limit), - gasPrice: this.web3.utils.toHex(this.config.gas_price), + gasPrice: this.web3.utils.toHex(gasPrice), to: this.readingContractAddress, }; @@ -840,10 +859,11 @@ class Ethereum { ); } - sendCommitment(importId, dvWallet, commitment) { + async sendCommitment(importId, dvWallet, commitment) { + const gasPrice = await this.getGasPrice(); const options = { gasLimit: this.web3.utils.toHex(this.config.gas_limit), - gasPrice: this.web3.utils.toHex(this.config.gas_price), + gasPrice: this.web3.utils.toHex(gasPrice), to: this.readingContractAddress, }; @@ -854,10 +874,11 @@ class Ethereum { ); } - initiateDispute(importId, dhWallet) { + async initiateDispute(importId, dhWallet) { + const gasPrice = await this.getGasPrice(); const options = { gasLimit: this.web3.utils.toHex(this.config.gas_limit), - gasPrice: this.web3.utils.toHex(this.config.gas_price), + gasPrice: this.web3.utils.toHex(gasPrice), to: this.readingContractAddress, }; @@ -868,10 +889,11 @@ class Ethereum { ); } - confirmPurchase(importId, dhWallet) { + async confirmPurchase(importId, dhWallet) { + const gasPrice = await this.getGasPrice(); const options = { gasLimit: this.web3.utils.toHex(this.config.gas_limit), - gasPrice: this.web3.utils.toHex(this.config.gas_price), + gasPrice: this.web3.utils.toHex(gasPrice), to: this.readingContractAddress, }; @@ -882,10 +904,11 @@ class Ethereum { ); } - cancelPurchase(importId, correspondentWallet, senderIsDh) { + async cancelPurchase(importId, correspondentWallet, senderIsDh) { + const gasPrice = await this.getGasPrice(); const options = { gasLimit: this.web3.utils.toHex(this.config.gas_limit), - gasPrice: this.web3.utils.toHex(this.config.gas_price), + gasPrice: this.web3.utils.toHex(gasPrice), to: this.readingContractAddress, }; @@ -896,13 +919,14 @@ class Ethereum { ); } - sendProofData( + async sendProofData( importId, dvWallet, checksumLeft, checksumRight, checksumHash, randomNumber1, randomNumber2, decryptionKey, blockIndex, ) { + const gasPrice = await this.getGasPrice(); const options = { gasLimit: this.web3.utils.toHex(this.config.gas_limit), - gasPrice: this.web3.utils.toHex(this.config.gas_price), + gasPrice: this.web3.utils.toHex(gasPrice), to: this.readingContractAddress, }; @@ -917,9 +941,10 @@ class Ethereum { } async sendEncryptedBlock(importId, dvWallet, encryptedBlock) { + const gasPrice = await this.getGasPrice(); const options = { gasLimit: this.web3.utils.toHex(this.config.gas_limit), - gasPrice: this.web3.utils.toHex(this.config.gas_price), + gasPrice: this.web3.utils.toHex(gasPrice), to: this.readingContractAddress, }; @@ -930,10 +955,11 @@ class Ethereum { ); } - payOutForReading(importId, dvWallet) { + async payOutForReading(importId, dvWallet, urgent) { + const gasPrice = await this.getGasPrice(urgent); const options = { gasLimit: this.web3.utils.toHex(this.config.gas_limit), - gasPrice: this.web3.utils.toHex(this.config.gas_price), + gasPrice: this.web3.utils.toHex(gasPrice), to: this.readingContractAddress, }; @@ -1041,10 +1067,11 @@ class Ethereum { * @param {string} - erc725identity * @param {string} - managementWallet */ - transferProfile(erc725identity, managementWallet) { + async transferProfile(erc725identity, managementWallet) { + const gasPrice = await this.getGasPrice(); const options = { gasLimit: this.web3.utils.toHex(this.config.gas_limit), - gasPrice: this.web3.utils.toHex(this.config.gas_price), + gasPrice: this.web3.utils.toHex(gasPrice), to: this.profileContractAddress, }; @@ -1081,14 +1108,16 @@ class Ethereum { * PayOut for multiple offers. * @returns {Promise} */ - payOutMultiple( + async payOutMultiple( blockchainIdentity, offerIds, + urgent, ) { const gasLimit = offerIds.length * 200000; + const gasPrice = await this.getGasPrice(urgent); const options = { gasLimit, - gasPrice: this.web3.utils.toHex(this.config.gas_price), + gasPrice: this.web3.utils.toHex(gasPrice), to: this.holdingContractAddress, }; this.log.trace(`payOutMultiple (identity=${blockchainIdentity}, offerIds=${offerIds}`); @@ -1164,6 +1193,20 @@ class Ethereum { from: this.config.wallet_address, }); } + + /** + * Returns gas price, throws error if not urgent and gas price higher than maximum allowed price + * @param urgent + * @returns {Promise<*|number>} + */ + async getGasPrice(urgent = false) { + const gasPrice = await this.gasPriceService.getGasPrice(); + if (gasPrice > this.config.max_allowed_gas_price && !urgent) { + throw new Error('Gas price higher than maximum allowed price'); + } else { + return gasPrice; + } + } } module.exports = Ethereum; diff --git a/modules/EventEmitter.js b/modules/EventEmitter.js index be3aab070f..ec2ce78673 100644 --- a/modules/EventEmitter.js +++ b/modules/EventEmitter.js @@ -461,6 +461,7 @@ class EventEmitter { dataSizeInBytes: dataSize, dataRootHash: root_hash, response: data.response, + urgent: data.urgent, }); } else { data.response.status(201); @@ -503,7 +504,7 @@ class EventEmitter { }); this._on('api-payout', async (data) => { - const { offerId } = data; + const { offerId, urgent } = data; logger.info(`Payout called for offer ${offerId}.`); const bid = await Models.bids.findOne({ where: { offer_id: offerId } }); @@ -512,6 +513,7 @@ class EventEmitter { name: 'dhPayOutCommand', delay: 0, data: { + urgent, offerId, }, }); @@ -535,6 +537,7 @@ class EventEmitter { holdingTimeInMinutes, tokenAmountPerHolder, litigationIntervalInMinutes, + urgent, } = data; let { @@ -566,7 +569,7 @@ class EventEmitter { const replicationId = await dcService.createOffer( dataSetId, dataRootHash, holdingTimeInMinutes, tokenAmountPerHolder, - dataSizeInBytes, litigationIntervalInMinutes, + dataSizeInBytes, litigationIntervalInMinutes, urgent, ); data.response.status(201); diff --git a/modules/command/dc/dc-offer-choose-command.js b/modules/command/dc/dc-offer-choose-command.js index d08d04739a..30002baaa7 100644 --- a/modules/command/dc/dc-offer-choose-command.js +++ b/modules/command/dc/dc-offer-choose-command.js @@ -24,6 +24,7 @@ class DCOfferChooseCommand extends Command { const { internalOfferId, excludedDHs, + urgent, } = command.data; const offer = await models.offers.findOne({ where: { id: internalOfferId } }); diff --git a/modules/command/dc/dc-offer-create-bc-command.js b/modules/command/dc/dc-offer-create-bc-command.js index 7f83769b9e..14620c0a0a 100644 --- a/modules/command/dc/dc-offer-create-bc-command.js +++ b/modules/command/dc/dc-offer-create-bc-command.js @@ -1,6 +1,7 @@ const Command = require('../command'); const Models = require('../../../models/index'); const Utilities = require('../../Utilities'); +const constants = require('../../constants'); /** * Creates offer on blockchain @@ -31,21 +32,36 @@ class DCOfferCreateBcCommand extends Command { tokenAmountPerHolder, dataSizeInBytes, litigationIntervalInMinutes, + urgent, } = command.data; - const result = await this.blockchain.createOffer( - Utilities.normalizeHex(this.config.erc725Identity), - dataSetId, - dataRootHash, - redLitigationHash, - greenLitigationHash, - blueLitigationHash, - Utilities.normalizeHex(this.config.identity), - holdingTimeInMinutes, - tokenAmountPerHolder, - dataSizeInBytes, - litigationIntervalInMinutes, - ); + let result; + + + try { + result = await this.blockchain.createOffer( + Utilities.normalizeHex(this.config.erc725Identity), + dataSetId, + dataRootHash, + redLitigationHash, + greenLitigationHash, + blueLitigationHash, + Utilities.normalizeHex(this.config.identity), + holdingTimeInMinutes, + tokenAmountPerHolder, + dataSizeInBytes, + litigationIntervalInMinutes, + urgent, + ); + } catch (error) { + if (error.message.includes('Gas price higher than maximum allowed price')) { + this.logger.info('Gas price too high, delaying call for 30 minutes'); + return Command.repeat(); + } + throw error; + } + + this.logger.important(`Offer with internal ID ${internalOfferId} for data set ${dataSetId} written to blockchain. Waiting for DHs...`); const offer = await Models.offers.findOne({ where: { id: internalOfferId } }); @@ -94,6 +110,7 @@ class DCOfferCreateBcCommand extends Command { const command = { name: 'dcOfferCreateBcCommand', delay: 0, + period: constants.GAS_PRICE_VALIDITY_TIME_IN_MILLS, transactional: false, }; Object.assign(command, map); diff --git a/modules/command/dc/dc-offer-create-db-command.js b/modules/command/dc/dc-offer-create-db-command.js index 92c030942a..5150958c9b 100644 --- a/modules/command/dc/dc-offer-create-db-command.js +++ b/modules/command/dc/dc-offer-create-db-command.js @@ -28,6 +28,7 @@ class DCOfferCreateDbCommand extends Command { holdingTimeInMinutes, tokenAmountPerHolder, litigationIntervalInMinutes, + urgent, } = command.data; const offer = await models.offers.findOne({ where: { id: internalOfferId } }); @@ -37,6 +38,7 @@ class DCOfferCreateDbCommand extends Command { offer.blue_litigation_hash = blueLitigationHash.toString('hex'); offer.green_litigation_hash = greenLitigationHash.toString('hex'); offer.litigation_interval_in_minutes = litigationIntervalInMinutes.toString(); + offer.urgent = !!urgent; offer.message = 'Offer has been prepared for BC.'; offer.status = 'PREPARED'; @@ -44,7 +46,7 @@ class DCOfferCreateDbCommand extends Command { fields: [ 'holding_time_in_minutes', 'token_amount_per_holder', 'red_litigation_hash', 'blue_litigation_hash', 'green_litigation_hash', - 'litigation_interval_in_minutes', 'message', 'status'], + 'litigation_interval_in_minutes', 'urgent', 'message', 'status'], }); this.remoteControl.offerUpdate({ id: internalOfferId, diff --git a/modules/command/dc/dc-offer-finalize-command.js b/modules/command/dc/dc-offer-finalize-command.js index c5eb392467..bf05c319b2 100644 --- a/modules/command/dc/dc-offer-finalize-command.js +++ b/modules/command/dc/dc-offer-finalize-command.js @@ -5,6 +5,8 @@ const Utilities = require('../../Utilities'); const Models = require('../../../models/index'); +const constants = require('../../constants'); + const { Op } = Models.Sequelize; @@ -30,6 +32,7 @@ class DCOfferFinalizeCommand extends Command { const { offerId, solution, + urgent, } = command.data; const nodeIdentifiers = solution.nodeIdentifiers.map(ni => @@ -49,16 +52,26 @@ class DCOfferFinalizeCommand extends Command { confirmations.push(replication.confirmation); } - await this.blockchain.finalizeOffer( - Utilities.normalizeHex(this.config.erc725Identity), - offerId, - new BN(solution.shift, 10), - confirmations[0], - confirmations[1], - confirmations[2], - colors, - nodeIdentifiers, - ); + try { + await this.blockchain.finalizeOffer( + Utilities.normalizeHex(this.config.erc725Identity), + offerId, + new BN(solution.shift, 10), + confirmations[0], + confirmations[1], + confirmations[2], + colors, + nodeIdentifiers, + urgent, + ); + } catch (error) { + if (error.message.includes('Gas price higher than maximum allowed price')) { + this.logger.info('Gas price too high, delaying call for 30 minutes'); + return Command.repeat(); + } + throw error; + } + return { commands: [ { @@ -133,6 +146,7 @@ class DCOfferFinalizeCommand extends Command { const command = { name: 'dcOfferFinalizeCommand', delay: 0, + period: constants.GAS_PRICE_VALIDITY_TIME_IN_MILLS, transactional: false, }; Object.assign(command, map); diff --git a/modules/command/dc/dc-offer-mining-completed-command.js b/modules/command/dc/dc-offer-mining-completed-command.js index 74ede81524..ac728e03b3 100644 --- a/modules/command/dc/dc-offer-mining-completed-command.js +++ b/modules/command/dc/dc-offer-mining-completed-command.js @@ -68,7 +68,7 @@ class DcOfferMiningCompletedCommand extends Command { throw new Error('Not enough tokens. To replicate data please deposit more tokens to your profile'); } - const commandData = { offerId, solution }; + const commandData = { offerId, solution, urgent: offer.urgent }; const commandSequence = ['dcOfferFinalizeCommand']; return { commands: [ diff --git a/modules/command/dh/dh-pay-out-command.js b/modules/command/dh/dh-pay-out-command.js index cf7d95e3b2..7351c68fa0 100644 --- a/modules/command/dh/dh-pay-out-command.js +++ b/modules/command/dh/dh-pay-out-command.js @@ -2,6 +2,7 @@ const Command = require('../command'); const Utilities = require('../../Utilities'); const Models = require('../../../models/index'); +const constants = require('../../constants'); /** * Starts token withdrawal operation @@ -23,6 +24,7 @@ class DhPayOutCommand extends Command { async execute(command) { const { offerId, + urgent, } = command.data; const bid = await Models.bids.findOne({ @@ -45,9 +47,17 @@ class DhPayOutCommand extends Command { const blockchainIdentity = Utilities.normalizeHex(this.config.erc725Identity); await this._printBalances(blockchainIdentity); - await this.blockchain.payOut(blockchainIdentity, offerId); - this.logger.important(`Payout for offer ${offerId} successfully completed.`); - await this._printBalances(blockchainIdentity); + try { + await this.blockchain.payOut(blockchainIdentity, offerId, urgent); + this.logger.important(`Payout for offer ${offerId} successfully completed.`); + await this._printBalances(blockchainIdentity); + } catch (error) { + if (error.message.includes('Gas price higher than maximum allowed price')) { + this.logger.info('Gas price too high, delaying call for 30 minutes'); + return Command.repeat(); + } + throw error; + } return Command.empty(); } @@ -77,6 +87,7 @@ class DhPayOutCommand extends Command { const command = { name: 'dhPayOutCommand', delay: 0, + period: constants.GAS_PRICE_VALIDITY_TIME_IN_MILLS, transactional: false, }; Object.assign(command, map); diff --git a/modules/constants.js b/modules/constants.js index 80ae809b0f..c3afd0d421 100644 --- a/modules/constants.js +++ b/modules/constants.js @@ -34,3 +34,15 @@ exports.MAX_COMMAND_DELAY_IN_MILLS = 14400 * 60 * 1000; // 10 days * @constant {number} DEFAULT_COMMAND_REPEAT_IN_MILLS - Default repeat interval */ exports.DEFAULT_COMMAND_REPEAT_INTERVAL_IN_MILLS = 5000; // 5 seconds + +/** + * + * @constant {number} GAS_PRICE_VALIDITY_TIME_IN_MILLS - gas price maximum validity time + */ +exports.GAS_PRICE_VALIDITY_TIME_IN_MILLS = 30 * 60 * 1000; // 30 min + +/** + * + * @constant {number} AVERAGE_GAS_PRICE_MULTIPLIER - gas price multiplier + */ +exports.AVERAGE_GAS_PRICE_MULTIPLIER = 1.2; // 30 min diff --git a/modules/controller/dc-controller.js b/modules/controller/dc-controller.js index 5d9ab69c21..e4ae15e310 100644 --- a/modules/controller/dc-controller.js +++ b/modules/controller/dc-controller.js @@ -42,6 +42,7 @@ class DCController { tokenAmountPerHolder: req.body.token_amount_per_holder, litigationIntervalInMinutes: req.body.litigation_interval_in_minutes, response: res, + urgent: req.body.urgent, }; this.emitter.emit('api-create-offer', queryObject); } else { diff --git a/modules/controller/import-controller.js b/modules/controller/import-controller.js index 4b275852bb..d46b106c4c 100644 --- a/modules/controller/import-controller.js +++ b/modules/controller/import-controller.js @@ -46,6 +46,7 @@ class ImportController { content, contact: req.contact, replicate: req.body.replicate, + urgent: req.body.urgent, response: res, }; this.emitter.emit(`api-${importtype}-import-request`, queryObject); @@ -61,6 +62,7 @@ class ImportController { content: req.body.importfile, contact: req.contact, replicate: req.body.replicate, + urgent: req.body.urgent, response: res, }; this.emitter.emit(`api-${importtype}-import-request`, queryObject); diff --git a/modules/service/axios-service.js b/modules/service/axios-service.js new file mode 100644 index 0000000000..e641521aaf --- /dev/null +++ b/modules/service/axios-service.js @@ -0,0 +1,17 @@ +const axios = require('axios'); + +class AxiosService { + async getGasPrice() { + const response = await axios.get('https://ethgasstation.info/json/ethgasAPI.json') + .catch((err) => { + this.log.warn(err); + return undefined; + }); + if (response) { + return response.data.average * 100000000; + } + return undefined; + } +} + +module.exports = AxiosService; diff --git a/modules/service/dc-service.js b/modules/service/dc-service.js index 29b08cd05e..6de1134aa0 100644 --- a/modules/service/dc-service.js +++ b/modules/service/dc-service.js @@ -29,7 +29,7 @@ class DCService { */ async createOffer( dataSetId, dataRootHash, holdingTimeInMinutes, tokenAmountPerHolder, - dataSizeInBytes, litigationIntervalInMinutes, + dataSizeInBytes, litigationIntervalInMinutes, urgent, ) { const offer = await models.offers.create({ data_set_id: dataSetId, @@ -64,6 +64,7 @@ class DCService { tokenAmountPerHolder, dataSizeInBytes, litigationIntervalInMinutes, + urgent, }; const commandSequence = [ 'dcOfferPrepareCommand', diff --git a/modules/service/gas-price-service.js b/modules/service/gas-price-service.js new file mode 100644 index 0000000000..06b7ba7317 --- /dev/null +++ b/modules/service/gas-price-service.js @@ -0,0 +1,48 @@ +const constants = require('../constants'); +const Web3 = require('web3'); + +class GasPriceService { + constructor(ctx) { + this.logger = ctx.logger; + this.config = ctx.config; + this.axiosService = ctx.axiosService; + this.web3 = ctx.web3; + } + + async getGasPrice() { + if (process.env.NODE_ENV !== 'mariner' && process.env.NODE_ENV !== 'production') { + return this.config.blockchain.gas_price; + } + + const now = new Date().getTime(); + if (this.config.blockchain.gas_price_last_update_timestamp + + constants.GAS_PRICE_VALIDITY_TIME_IN_MILLS > now) { + return this.config.blockchain.gas_price; + } + const axiosGasPrice = await this.axiosService.getGasPrice() + .catch((err) => { this.logger.warn(err); }) * constants.AVERAGE_GAS_PRICE_MULTIPLIER; + + const web3GasPrice = await this.web3.eth.getGasPrice() + .catch((err) => { this.logger.warn(err); }) * constants.AVERAGE_GAS_PRICE_MULTIPLIER; + + if (axiosGasPrice && web3GasPrice) { + const gasPrice = (axiosGasPrice > web3GasPrice ? axiosGasPrice : web3GasPrice); + this.saveNewGasPriceAndTime(gasPrice); + return gasPrice; + } else if (axiosGasPrice) { + this.saveNewGasPriceAndTime(axiosGasPrice); + return axiosGasPrice; + } else if (web3GasPrice) { + this.saveNewGasPriceAndTime(web3GasPrice); + return web3GasPrice; + } + return this.config.blockchain.gas_price; + } + + saveNewGasPriceAndTime(gasPrice) { + this.config.blockchain.gas_price = gasPrice; + this.config.blockchain.gas_price_last_update_timestamp = new Date().getTime(); + } +} + +module.exports = GasPriceService; diff --git a/modules/service/profile-service.js b/modules/service/profile-service.js index 893d36f954..6012028b1b 100644 --- a/modules/service/profile-service.js +++ b/modules/service/profile-service.js @@ -3,6 +3,7 @@ const BN = require('bn.js'); const path = require('path'); const Utilities = require('../Utilities'); +const constants = require('../constants'); class ProfileService { /** @@ -45,26 +46,71 @@ class ProfileService { initialTokenAmount = new BN(profileMinStake, 10); } - await this.blockchain.increaseProfileApproval(initialTokenAmount); + let approvalIncreased = false; + do { + try { + // eslint-disable-next-line no-await-in-loop + await this.blockchain.increaseProfileApproval(initialTokenAmount); + approvalIncreased = true; + } catch (error) { + if (error.message.includes('Gas price higher than maximum allowed price')) { + this.logger.warn('Current average gas price is too high, to force profile' + + ' creation increase max_allowed_gas_price in your configuration file and reset the node.' + + ' Retrying in 30 minutes...'); + // eslint-disable-next-line no-await-in-loop + await new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, constants.GAS_PRICE_VALIDITY_TIME_IN_MILLS); + }); + } else { + throw error; + } + } + } while (approvalIncreased === false); // set empty identity if there is none const identity = this.config.erc725Identity ? this.config.erc725Identity : new BN(0, 16); - if (this.config.management_wallet) { - await this.blockchain.createProfile( - this.config.management_wallet, - this.config.identity, - initialTokenAmount, identityExists, identity, - ); - } else { - this.logger.important('Management wallet not set. Creating profile with operating wallet only.' + - ' Please set management one.'); - await this.blockchain.createProfile( - this.config.node_wallet, - this.config.identity, - initialTokenAmount, identityExists, identity, - ); - } + let createProfileCalled = false; + do { + try { + if (this.config.management_wallet) { + // eslint-disable-next-line no-await-in-loop + await this.blockchain.createProfile( + this.config.management_wallet, + this.config.identity, + initialTokenAmount, identityExists, identity, + ); + createProfileCalled = true; + } else { + this.logger.important('Management wallet not set. Creating profile with operating wallet only.' + + ' Please set management one.'); + // eslint-disable-next-line no-await-in-loop + await this.blockchain.createProfile( + this.config.node_wallet, + this.config.identity, + initialTokenAmount, identityExists, identity, + ); + createProfileCalled = true; + } + } catch (error) { + if (error.message.includes('Gas price higher than maximum allowed price')) { + this.logger.warn('Current average gas price is too high, to force profile' + + ' creation increase max_allowed_gas_price in your configuration file and reset the node.' + + ' Retrying in 30 minutes...'); + // eslint-disable-next-line no-await-in-loop + await new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, constants.GAS_PRICE_VALIDITY_TIME_IN_MILLS); + }); + } else { + throw error; + } + } + } while (createProfileCalled === false); + if (!identityExists) { const event = await this.blockchain.subscribeToEvent('IdentityCreated', null, 5 * 60 * 1000, null, eventData => Utilities.compareHexStrings(eventData.profile, this.config.node_wallet)); if (event) { diff --git a/ot-node.js b/ot-node.js index 738c4a4533..a2d2592fb1 100644 --- a/ot-node.js +++ b/ot-node.js @@ -29,6 +29,7 @@ const uuidv4 = require('uuid/v4'); const awilix = require('awilix'); const homedir = require('os').homedir(); const argv = require('minimist')(process.argv.slice(2)); +const AxiosService = require('./modules/service/axios-service'); const Graph = require('./modules/Graph'); const Product = require('./modules/Product'); @@ -44,6 +45,7 @@ const APIUtilities = require('./modules/utility/api-utilities'); const RestAPIService = require('./modules/service/rest-api-service'); const M1PayoutAllMigration = require('./modules/migration/m1-payout-all-migration'); const M2SequelizeMetaMigration = require('./modules/migration/m2-sequelize-meta-migration'); +const GasPriceService = require('./modules/service/gas-price-service'); const pjson = require('./package.json'); const configjson = require('./config/config.json'); @@ -374,6 +376,8 @@ class OTNode { minerService: awilix.asClass(MinerService).singleton(), replicationService: awilix.asClass(ReplicationService).singleton(), restAPIService: awilix.asClass(RestAPIService).singleton(), + axiosService: awilix.asClass(AxiosService).singleton(), + gasPriceService: awilix.asClass(GasPriceService).singleton(), }); const blockchain = container.resolve('blockchain'); await blockchain.initialize(); @@ -528,6 +532,8 @@ class OTNode { transport: awilix.asValue(Transport()), apiUtilities: awilix.asClass(APIUtilities).singleton(), restAPIService: awilix.asClass(RestAPIService).singleton(), + axiosService: awilix.asClass(AxiosService).singleton(), + gasPriceService: awilix.asClass(GasPriceService).singleton(), }); const transport = container.resolve('transport'); diff --git a/package-lock.json b/package-lock.json index bec7456b04..fd2f961ae5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.58", + "version": "2.0.59", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 37561b9f0c..207e250e99 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "2.0.58", + "version": "2.0.59", "description": "OriginTrail node", "main": ".eslintrc.js", "config": { diff --git a/test/modules/service/gas-price-service-test.js b/test/modules/service/gas-price-service-test.js new file mode 100644 index 0000000000..a3a20958cf --- /dev/null +++ b/test/modules/service/gas-price-service-test.js @@ -0,0 +1,111 @@ +const { + describe, before, beforeEach, it, +} = require('mocha'); +const GasPriceService = require('../../../modules/service/gas-price-service'); +const awilix = require('awilix'); +const defaultConfig = require('../../../config/config.json').mariner; +const rc = require('rc'); +const pjson = require('../../../package.json'); +const logger = require('../../../modules/logger'); +const { assert } = require('chai'); +const constants = require('../../../modules/constants'); + +let gasPriceService; +let axiosServiceMock; +let web3ServiceMock; + +const defaultConfigGasPrice = 30000000000; +const defaultAxiosGasPrice = 20000000000; +const defaultWeb3GasPrice = 10000000000; +let config; + +class AxiosServiceMock { + constructor(logger) { + this.logger = logger; + this.gasPrice = defaultAxiosGasPrice; + this.logger.debug('Axios service mock initialized'); + } + + async getGasPrice() { + this.logger.debug('Returning axios service gas price'); + return this.gasPrice; + } +} + +class Web3Mock { + constructor(logger) { + this.logger = logger; + this.eth = { + gasPrice: defaultWeb3GasPrice, + async getGasPrice() { + return this.gasPrice; + }, + }; + } +} + +describe('Gas price service test', () => { + beforeEach('Setup container', async () => { + // Create the container and set the injectionMode to PROXY (which is also the default). + process.env.NODE_ENV = 'mariner'; + const container = awilix.createContainer({ + injectionMode: awilix.InjectionMode.PROXY, + }); + + config = rc(pjson.name, defaultConfig); + config.blockchain.gas_price = defaultConfigGasPrice; + config.blockchain.gas_price_last_update_timestamp = 0; + axiosServiceMock = new AxiosServiceMock(logger); + web3ServiceMock = new Web3Mock(logger); + container.register({ + config: awilix.asValue(config), + logger: awilix.asValue(logger), + axiosService: awilix.asValue(axiosServiceMock), + web3: awilix.asValue(web3ServiceMock), + }); + gasPriceService = new GasPriceService(container.cradle); + }); + + it('Get gas price - env is develop - expect default is returned', async () => { + process.env.NODE_ENV = 'develop'; + const gasPrice = await gasPriceService.getGasPrice(); + assert.equal(gasPrice, defaultConfigGasPrice, 'Gas price should be the same as in config'); + }); + + it('Get gas price - env is mariner, all services return valid value - expect axios value is used', async () => { + const gasPrice = await gasPriceService.getGasPrice(); + assert.equal(gasPrice, defaultAxiosGasPrice * constants.AVERAGE_GAS_PRICE_MULTIPLIER, 'Returned gas price price should be the same as default axios gas price'); + assert.equal(config.blockchain.gas_price, defaultAxiosGasPrice * constants.AVERAGE_GAS_PRICE_MULTIPLIER, 'Configuration gas price should be the same as default axios gas price'); + const now = new Date().getTime(); + assert.closeTo(config.blockchain.gas_price_last_update_timestamp, now, 1000, 'Now should be set as new timestamp'); + }); + + it('Get gas price - env is mariner, all services return valid value, timestamp is not older than 30 min - expect config value is used', async () => { + const lastUpdateTimestamp = new Date().getTime() - (1000 * 25); + config.blockchain.gas_price_last_update_timestamp = lastUpdateTimestamp; + gasPriceService.config = config; + const gasPrice = await gasPriceService.getGasPrice(); + assert.equal(gasPrice, config.blockchain.gas_price, 'Gas price should be the same as default config'); + assert.equal(config.blockchain.gas_price_last_update_timestamp, lastUpdateTimestamp, 'Timestamp should not be changed'); + }); + + it('Get gas price - env is mariner, axios returns undefined - expect web3 value is used', async () => { + axiosServiceMock.gasPrice = undefined; + gasPriceService.axiosService = axiosServiceMock; + const gasPrice = await gasPriceService.getGasPrice(); + assert.equal(gasPrice, defaultWeb3GasPrice * constants.AVERAGE_GAS_PRICE_MULTIPLIER, 'Gas price should be the same as default web3'); + assert.equal(config.blockchain.gas_price, defaultWeb3GasPrice * constants.AVERAGE_GAS_PRICE_MULTIPLIER, 'Gas price should be the same as default web3'); + const now = new Date().getTime(); + assert.closeTo(config.blockchain.gas_price_last_update_timestamp, now, 1000, 'Timestamp should not be changed'); + }); + + it('Get gas price - env is mariner, web3 returns undefined - expect axios value is used', async () => { + web3ServiceMock.eth.gasPrice = undefined; + gasPriceService.web3 = web3ServiceMock; + const gasPrice = await gasPriceService.getGasPrice(); + assert.equal(gasPrice, defaultAxiosGasPrice * constants.AVERAGE_GAS_PRICE_MULTIPLIER, 'Gas price should be the same as default axios'); + assert.equal(config.blockchain.gas_price, defaultAxiosGasPrice * constants.AVERAGE_GAS_PRICE_MULTIPLIER, 'Gas price should be the same as default axios'); + const now = new Date().getTime(); + assert.closeTo(config.blockchain.gas_price_last_update_timestamp, now, 1000, 'Timestamp should not be changed'); + }); +}); diff --git a/test/modules/utilities.test.js b/test/modules/utilities.test.js index 31cda13839..140b250fc8 100644 --- a/test/modules/utilities.test.js +++ b/test/modules/utilities.test.js @@ -36,7 +36,7 @@ describe('Utilities module', () => { ); assert.hasAllKeys( config.blockchain, [ - 'blockchain_title', 'network_id', 'gas_limit', 'gas_price', + 'blockchain_title', 'network_id', 'gas_limit', 'gas_price', 'max_allowed_gas_price', 'hub_contract_address', 'plugins'], `Some config items are missing in config.blockchain for environment '${environment}'`, ); @@ -90,7 +90,7 @@ describe('Utilities module', () => { environments.forEach((environment) => { const config = configJson[environment]; assert.hasAllKeys(config.blockchain, ['blockchain_title', 'network_id', 'gas_limit', 'plugins', - 'gas_price', 'hub_contract_address']); + 'gas_price', 'max_allowed_gas_price', 'hub_contract_address']); assert.equal(config.blockchain.blockchain_title, 'Ethereum'); }); });