diff --git a/.github/workflows/test_and_release.yml b/.github/workflows/test_and_release.yml index 6b64e4c1b..49f148384 100644 --- a/.github/workflows/test_and_release.yml +++ b/.github/workflows/test_and_release.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/setup-node@v2 with: - node-version: '12' + node-version: '18' - name: Enable NPM cache uses: actions/cache@v2 diff --git a/.jshintrc b/.jshintrc index 1b7ebf23f..df33758a7 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,15 +1,15 @@ { + "esversion": 11, "bitwise": false, "browser": true, "camelcase": false, "curly": true, "devel": false, "eqeqeq": true, - "esnext": true, "freeze": true, "immed": true, "indent": 2, - "latedef": true, + "latedef": "nofunc", "newcap": false, "noarg": true, "node": true, diff --git a/lib/services/dashd.js b/lib/services/dashd.js index adc2d12bc..3e953d598 100644 --- a/lib/services/dashd.js +++ b/lib/services/dashd.js @@ -21,6 +21,17 @@ var utils = require('../utils'); var Service = require('../service'); var rawtxTopicBuffer = new Buffer('7261777478', 'hex'); var rawtxlockTopicBuffer = new Buffer('72617774786c6f636b', 'hex'); + +function isNonFatalError(err) { + if (err.code === Dash.E_RPC_IN_WARMUP) { + log.warn(err.message); + return true; + } + + // will throw instead of retrying + return false; +} + /** * Provides a friendly event driven API to dashd in Node.js. Manages starting and * stopping dashd as a child process for application support, as well as connecting @@ -77,6 +88,7 @@ Dash.DEFAULT_SPAWN_RESTART_TIME = 5000; Dash.DEFAULT_SPAWN_STOP_TIME = 10000; Dash.DEFAULT_TRY_ALL_INTERVAL = 1000; Dash.DEFAULT_REINDEX_INTERVAL = 10000; +Dash.DEFAULT_START_RETRY_TIMES = 60; Dash.DEFAULT_START_RETRY_INTERVAL = 5000; Dash.DEFAULT_TIP_UPDATE_INTERVAL = 15000; Dash.DEFAULT_TRANSACTION_CONCURRENCY = 5; @@ -96,6 +108,7 @@ Dash.DEFAULT_CONFIG_SETTINGS = { rpcpassword: 'local321', uacomment: 'dashcore' }; +Dash.E_RPC_IN_WARMUP = -28; Dash.prototype._initDefaults = function(options) { /* jshint maxcomplexity: 15 */ @@ -113,6 +126,7 @@ Dash.prototype._initDefaults = function(options) { // try all interval this.tryAllInterval = options.tryAllInterval || Dash.DEFAULT_TRY_ALL_INTERVAL; + this.startRetryTimes = options.startRetryTimes || Dash.DEFAULT_START_RETRY_TIMES; this.startRetryInterval = options.startRetryInterval || Dash.DEFAULT_START_RETRY_INTERVAL; // rpc limits @@ -856,10 +870,7 @@ Dash.prototype._checkReindex = function(node, callback) { Dash.prototype._loadTipFromNode = function(node, callback) { var self = this; node.client.getBestBlockHash(function(err, response) { - if (err && err.code === -28) { - log.warn(err.message); - return callback(self._wrapRPCError(err)); - } else if (err) { + if (err) { return callback(self._wrapRPCError(err)); } node.client.getBlock(response.result, function(err, response) { @@ -963,7 +974,12 @@ Dash.prototype._spawnChildProcess = function(callback) { var exitShutdown = false; - async.retry({times: 60, interval: self.startRetryInterval}, function(done) { + var retryOpts = { + times: self.startRetryTimes, + interval: self.startRetryInterval, + errorFilter: isNonFatalError, + }; + async.retry(retryOpts, function (done) { if (self.node.stopping) { exitShutdown = true; return done(); @@ -1008,7 +1024,12 @@ Dash.prototype._connectProcess = function(config, callback) { var node = {}; var exitShutdown = false; - async.retry({times: 60, interval: self.startRetryInterval}, function(done) { + var retryOpts = { + times: self.startRetryTimes, + interval: self.startRetryInterval, + errorFilter: isNonFatalError, + }; + async.retry(retryOpts, function(done) { if (self.node.stopping) { exitShutdown = true; return done(); diff --git a/test/services/dashd.unit.js b/test/services/dashd.unit.js index 4bc07bf18..22eb397dd 100644 --- a/test/services/dashd.unit.js +++ b/test/services/dashd.unit.js @@ -38,6 +38,11 @@ describe('Dash Service', function() { } }; + var RPC_IN_WARMUP_ERROR = new Error('Verifying blocks...'); + Object.assign(RPC_IN_WARMUP_ERROR, { + code: DashService.E_RPC_IN_WARMUP + }) + describe('@constructor', function() { it('will create an instance', function() { var dashd = new DashService(baseConfig); @@ -1592,9 +1597,9 @@ describe('Dash Service', function() { done(); }); }); - it('will log when error is RPC_IN_WARMUP', function(done) { + it('will throw when error is RPC_IN_WARMUP outside of retry', function(done) { var dashd = new DashService(baseConfig); - var getBestBlockHash = sinon.stub().callsArgWith(0, {code: -28, message: 'Verifying blocks...'}); + var getBestBlockHash = sinon.stub().callsArgWith(0, RPC_IN_WARMUP_ERROR); var node = { client: { getBestBlockHash: getBestBlockHash @@ -1602,7 +1607,7 @@ describe('Dash Service', function() { }; dashd._loadTipFromNode(node, function(err) { err.should.be.instanceof(Error); - log.warn.callCount.should.equal(1); + log.warn.callCount.should.equal(0); done(); }); }); @@ -1968,7 +1973,7 @@ describe('Dash Service', function() { process.emit('exit', 1); }); }); - it('will give error after 60 retries', function(done) { + it('will give error after 60 retries of RPC_IN_WARMUP warning', function(done) { var process = new EventEmitter(); var spawn = sinon.stub().returns(process); var TestDashService = proxyquire('../../lib/services/dashd', { @@ -1992,13 +1997,44 @@ describe('Dash Service', function() { dashd.spawn.config.rpcpassword = 'password'; dashd.spawn.config.zmqpubrawtx = 'tcp://127.0.0.1:30001'; dashd.spawn.config.zmqpubrawtxlock = 'tcp://127.0.0.1:30001'; - dashd._loadTipFromNode = sinon.stub().callsArgWith(1, new Error('test')); + dashd._loadTipFromNode = sinon.stub().callsArgWith(1, RPC_IN_WARMUP_ERROR); dashd._spawnChildProcess(function(err) { dashd._loadTipFromNode.callCount.should.equal(60); err.should.be.instanceof(Error); done(); }); }); + it('will give error WITHOUT retrying for fatal and unknown errors', function(done) { + var process = new EventEmitter(); + var spawn = sinon.stub().returns(process); + var TestDashService = proxyquire('../../lib/services/dashd', { + fs: { + readFileSync: readFileSync + }, + child_process: { + spawn: spawn + } + }); + var dashd = new TestDashService(baseConfig); + dashd.startRetryInterval = 1; + dashd._loadSpawnConfiguration = sinon.stub(); + dashd.spawn = {}; + dashd.spawn.exec = 'testexec'; + dashd.spawn.configPath = 'testdir/dash.conf'; + dashd.spawn.datadir = 'testdir'; + dashd.spawn.config = {}; + dashd.spawn.config.rpcport = 20001; + dashd.spawn.config.rpcuser = 'dash'; + dashd.spawn.config.rpcpassword = 'password'; + dashd.spawn.config.zmqpubrawtx = 'tcp://127.0.0.1:30001'; + dashd.spawn.config.zmqpubrawtxlock = 'tcp://127.0.0.1:30001'; + dashd._loadTipFromNode = sinon.stub().callsArgWith(1, new Error('test')); + dashd._spawnChildProcess(function(err) { + dashd._loadTipFromNode.callCount.should.equal(1); + err.should.be.instanceof(Error); + done(); + }); + }); it('will give error from check reindex', function(done) { var process = new EventEmitter(); var spawn = sinon.stub().returns(process); @@ -2058,9 +2094,9 @@ describe('Dash Service', function() { done(); }); }); - it('will give error from loadTipFromNode after 60 retries', function(done) { + it('will give error for warnings from loadTipFromNode after 60 retries', function(done) { var dashd = new DashService(baseConfig); - dashd._loadTipFromNode = sinon.stub().callsArgWith(1, new Error('test')); + dashd._loadTipFromNode = sinon.stub().callsArgWith(1, RPC_IN_WARMUP_ERROR); dashd.startRetryInterval = 1; var config = {}; dashd._connectProcess(config, function(err) { @@ -2069,6 +2105,17 @@ describe('Dash Service', function() { done(); }); }); + it('will immediately fail from loadTipFromNode for fatal and unknown errors', function(done) { + var dashd = new DashService(baseConfig); + dashd._loadTipFromNode = sinon.stub().callsArgWith(1, new Error('test')); + dashd.startRetryInterval = 1; + var config = {}; + dashd._connectProcess(config, function(err) { + err.should.be.instanceof(Error); + dashd._loadTipFromNode.callCount.should.equal(1); + done(); + }); + }); it('will init zmq/rpc on node', function(done) { var dashd = new DashService(baseConfig); dashd._initZmqSubSocket = sinon.stub();