From 61580d6f9be18e63d90fe684625bb5b400d80e26 Mon Sep 17 00:00:00 2001
From: cgeek <cem.moreau@gmail.com>
Date: Sun, 4 Jun 2017 00:16:53 +0200
Subject: [PATCH] [enh] #957 Add `msPeriod` parameter

---
 app/lib/computation/blockchainContext.js    |  2 +
 app/lib/constants.js                        |  3 +
 app/lib/dal/fileDAL.js                      | 20 +++--
 app/lib/dal/sqliteDAL/MetaDAL.js            | 23 ++++-
 app/lib/dal/sqliteDAL/index/MIndexDAL.js    |  1 +
 doc/Protocol.md                             | 16 ++++
 index.js                                    |  2 +-
 package.json                                |  4 +-
 server.js                                   |  9 +-
 test/dal/dal.js                             |  2 +-
 test/integration/membership_chainability.js | 93 +++++++++++++++++++++
 test/integration/tools/toolbox.js           |  7 +-
 yarn.lock                                   | 54 +++++++-----
 13 files changed, 197 insertions(+), 39 deletions(-)
 create mode 100644 test/integration/membership_chainability.js

diff --git a/app/lib/computation/blockchainContext.js b/app/lib/computation/blockchainContext.js
index 7cc75c5be..87cf93d00 100644
--- a/app/lib/computation/blockchainContext.js
+++ b/app/lib/computation/blockchainContext.js
@@ -154,6 +154,8 @@ function BlockchainContext(BlockchainService) {
     if (indexer.ruleIdentityWritability(iindex, conf) === false) throw Error('ruleIdentityWritability');
     // BR_G64
     if (indexer.ruleMembershipWritability(mindex, conf) === false) throw Error('ruleMembershipWritability');
+    // BR_G108
+    if (indexer.ruleMembershipPeriod(mindex) === false) throw Error('ruleMembershipPeriod');
     // BR_G65
     if (indexer.ruleCertificationWritability(cindex, conf) === false) throw Error('ruleCertificationWritability');
     // BR_G66
diff --git a/app/lib/constants.js b/app/lib/constants.js
index aefa526e6..0e8e784f7 100644
--- a/app/lib/constants.js
+++ b/app/lib/constants.js
@@ -11,6 +11,8 @@ const IPV6_REGEXP = /^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,
 
 module.exports = {
 
+  TIME_TO_TURN_ON_BRG_107: 1498860000,
+
   ERROR: {
 
     PEER: {
@@ -124,6 +126,7 @@ module.exports = {
       STEPMAX: 3,
       SIGDELAY: 3600 * 24 * 365 * 5,
       SIGPERIOD: 0, // Instant
+      MSPERIOD: 0, // Instant
       SIGSTOCK: 40,
       SIGWINDOW: 3600 * 24 * 7, // a week
       SIGVALIDITY: 3600 * 24 * 365,
diff --git a/app/lib/dal/fileDAL.js b/app/lib/dal/fileDAL.js
index cb83fd43c..9742f9f40 100644
--- a/app/lib/dal/fileDAL.js
+++ b/app/lib/dal/fileDAL.js
@@ -62,14 +62,14 @@ function FileDAL(params) {
     'cindexDAL': that.cindexDAL
   };
 
-  this.init = () => co(function *() {
+  this.init = (conf) => co(function *() {
     const dalNames = _.keys(that.newDals);
     for (const dalName of dalNames) {
       const dal = that.newDals[dalName];
       yield dal.init();
     }
     logger.debug("Upgrade database...");
-    yield that.metaDAL.upgradeDatabase();
+    yield that.metaDAL.upgradeDatabase(conf);
     const latestMember = yield that.iindexDAL.getLatestMember();
     if (latestMember && that.wotb.getWoTSize() > latestMember.wotb_id + 1) {
       logger.warn('Maintenance: cleaning wotb...');
@@ -374,9 +374,19 @@ function FileDAL(params) {
     return _(pending).sortBy((ms) => -ms.number)[0];
   });
 
-  this.findNewcomers = () => co(function*() {
-    const mss = yield that.msDAL.getPendingIN();
-    return _.chain(mss).sortBy((ms) => -ms.sigDate).value();
+  this.findNewcomers = (blockMedianTime) => co(function*() {
+    const pending = yield that.msDAL.getPendingIN()
+    const mss = yield pending.map(p => co(function*() {
+      const reduced = yield that.mindexDAL.getReducedMS(p.issuer)
+      if (!reduced || !reduced.chainable_on || blockMedianTime >= reduced.chainable_on || blockMedianTime < constants.TIME_TO_TURN_ON_BRG_107) {
+        return p
+      }
+      return null
+    }))
+    return _.chain(mss)
+      .filter(ms => ms)
+      .sortBy((ms) => -ms.sigDate)
+      .value()
   });
 
   this.findLeavers = () => co(function*() {
diff --git a/app/lib/dal/sqliteDAL/MetaDAL.js b/app/lib/dal/sqliteDAL/MetaDAL.js
index 017cdf2b2..b90353c54 100644
--- a/app/lib/dal/sqliteDAL/MetaDAL.js
+++ b/app/lib/dal/sqliteDAL/MetaDAL.js
@@ -267,6 +267,21 @@ function MetaDAL(driver) {
         wallet.balance = amountsRemaining.reduce((sum, src) => sum + src.amount * Math.pow(10, src.base), 0)
         yield walletDAL.saveWallet(wallet)
       }
+    }),
+
+    /**
+     * Feeds the m_index.chainable_on
+     */
+    21: (conf) => co(function*() {
+      let blockDAL = new (require('./BlockDAL'))(driver);
+      let mindexDAL = new (require('./index/MIndexDAL'))(driver);
+      yield mindexDAL.exec('ALTER TABLE m_index ADD COLUMN chainable_on INTEGER NULL;')
+      const memberships = yield mindexDAL.query('SELECT * FROM m_index WHERE op = ?', [common.constants.IDX_CREATE])
+      for (const ms of memberships) {
+        const reference = yield blockDAL.getBlock(parseInt(ms.written_on.split('-')[0]))
+        const updateQuery = 'UPDATE m_index SET chainable_on = ' + (reference.medianTime + conf.msPeriod) + ' WHERE pub = \'' + ms.pub + '\' AND op = \'CREATE\''
+        yield mindexDAL.exec(updateQuery)
+      }
     })
   };
 
@@ -280,7 +295,7 @@ function MetaDAL(driver) {
       'COMMIT;', []);
   });
 
-  function executeMigration(migration) {
+  function executeMigration(migration, conf) {
     return co(function *() {
       try {
         if (typeof migration == "string") {
@@ -291,7 +306,7 @@ function MetaDAL(driver) {
         } else if (typeof migration == "function") {
 
           // JS function to execute
-          yield migration();
+          yield migration(conf);
 
         }
       } catch (e) {
@@ -300,10 +315,10 @@ function MetaDAL(driver) {
     });
   }
 
-  this.upgradeDatabase = () => co(function *() {
+  this.upgradeDatabase = (conf) => co(function *() {
     let version = yield that.getVersion();
     while(migrations[version]) {
-      yield executeMigration(migrations[version]);
+      yield executeMigration(migrations[version], conf);
       // Automated increment
       yield that.exec('UPDATE meta SET version = version + 1');
       version++;
diff --git a/app/lib/dal/sqliteDAL/index/MIndexDAL.js b/app/lib/dal/sqliteDAL/index/MIndexDAL.js
index 72052c6f9..ab8ae3876 100644
--- a/app/lib/dal/sqliteDAL/index/MIndexDAL.js
+++ b/app/lib/dal/sqliteDAL/index/MIndexDAL.js
@@ -28,6 +28,7 @@ function MIndexDAL(driver) {
     'expired_on',
     'revokes_on',
     'revoked_on',
+    'chainable_on',
     'leaving',
     'revocation'
   ];
diff --git a/doc/Protocol.md b/doc/Protocol.md
index 8d4bded3b..612afb4ea 100644
--- a/doc/Protocol.md
+++ b/doc/Protocol.md
@@ -1057,6 +1057,7 @@ ud0         | UD(0), i.e. initial Universal Dividend
 udTime0     | Time of first UD.
 udReevalTime0 | Time of first reevaluation of the UD.
 sigPeriod   | Minimum delay between 2 certifications of a same issuer, in seconds. Must be positive or zero.
+msPeriod    | Minimum delay between 2 memberships of a same issuer, in seconds. Must be positive or zero.
 sigStock    | Maximum quantity of active certifications made by member.
 sigWindow   | Maximum delay a certification can wait before being expired for non-writing.
 sigValidity | Maximum age of an active signature (in seconds)
@@ -1198,6 +1199,7 @@ Each identity produces 2 new entries:
         expired_on = 0
         expires_on = MedianTime + msValidity
         revokes_on = MedianTime + msValidity*2
+        chainable_on = MedianTime + msPeriod
         type = 'JOIN'
         revoked_on = null
         leaving = false
@@ -1226,6 +1228,7 @@ Each join whose `PUBLIC_KEY` **does not match** a local MINDEX `CREATE, PUBLIC_K
         expired_on = 0
         expires_on = MedianTime + msValidity
         revokes_on = MedianTime + msValidity*2
+        chainable_on = MedianTime + msPeriod
         type = 'JOIN'
         revoked_on = null
         leaving = null
@@ -1242,6 +1245,7 @@ Each active produces 1 new entry:
         written_on = BLOCKSTAMP
         expires_on = MedianTime + msValidity
         revokes_on = MedianTime + msValidity*2
+        chainable_on = MedianTime + msPeriod
         type = 'RENEW'
         revoked_on = null
         leaving = null
@@ -2088,6 +2092,12 @@ For each ENTRY in local MINDEX where `revoked_on == null`:
 For each ENTRY in local MINDEX where `revoked_on != null`:
 
     ENTRY.isBeingRevoked = true
+   
+####### BR_G107 - ENTRY.unchainables
+F
+If `HEAD.number > 0`:
+
+    ENTRY.unchainables = COUNT(GLOBAL_MINDEX[issuer=ENTRY.issuer, chainable_on > HEAD~1.medianTime]))
     
 ###### Local CINDEX augmentation
 
@@ -2327,6 +2337,12 @@ Rule:
 
     ENTRY.age <= [msWindow]
 
+###### BR_G108 - Membership period
+
+Rule:
+
+    ENTRY.unchainables == 0
+
 ###### BR_G65 - Certification writability
 
 Rule:
diff --git a/index.js b/index.js
index 56cd2c9f9..a96bed320 100644
--- a/index.js
+++ b/index.js
@@ -296,7 +296,7 @@ function Stack(dependencies) {
         return yield command.onConfiguredExecute(server, conf, program, params, wizardTasks, that);
       }
       // Second possible class of commands: post-service
-      yield server.initDAL();
+      yield server.initDAL(conf);
 
       /**
        * Service injection
diff --git a/package.json b/package.json
index 3d9bd6949..cd215319e 100644
--- a/package.json
+++ b/package.json
@@ -42,7 +42,7 @@
     "colors": "1.1.2",
     "commander": "2.9.0",
     "daemonize2": "0.4.2",
-    "duniter-common": "1.3.x",
+    "duniter-common": "^1.3.5",
     "event-stream": "3.3.4",
     "heapdump": "^0.3.9",
     "inquirer": "3.0.6",
@@ -71,7 +71,7 @@
     "duniter-bma": "1.3.x",
     "duniter-crawler": "1.3.x",
     "duniter-keypair": "1.3.X",
-    "duniter-prover": "1.3.x",
+    "duniter-prover": "^1.3.3",
     "duniter-ui": "1.3.x",
     "eslint": "3.13.1",
     "eslint-plugin-mocha": "4.8.0",
diff --git a/server.js b/server.js
index 8c9bff582..bdb435dfb 100644
--- a/server.js
+++ b/server.js
@@ -89,6 +89,7 @@ function Server (home, memoryOnly, overrideConf) {
       ud0:                constants.CONTRACT.DEFAULT.UD0,
       stepMax:            constants.CONTRACT.DEFAULT.STEPMAX,
       sigPeriod:          constants.CONTRACT.DEFAULT.SIGPERIOD,
+      msPeriod:           constants.CONTRACT.DEFAULT.MSPERIOD,
       sigStock:           constants.CONTRACT.DEFAULT.SIGSTOCK,
       sigWindow:          constants.CONTRACT.DEFAULT.SIGWINDOW,
       sigValidity:        constants.CONTRACT.DEFAULT.SIGVALIDITY,
@@ -110,6 +111,8 @@ function Server (home, memoryOnly, overrideConf) {
         that.conf[key] = defaultValues[key];
       }
     });
+    // 1.3.X: the msPeriod = msWindow
+    that.conf.msPeriod = that.conf.msPeriod || that.conf.msWindow
     // Default keypair
     if (!that.conf.pair || !that.conf.pair.pub || !that.conf.pair.sec) {
       // Create a random key
@@ -171,8 +174,8 @@ function Server (home, memoryOnly, overrideConf) {
 
   this.submitP = (obj, isInnerWrite) => Q.nbind(this.submit, this)(obj, isInnerWrite);
 
-  this.initDAL = () => co(function*() {
-    yield that.dal.init();
+  this.initDAL = (conf) => co(function*() {
+    yield that.dal.init(conf);
     // Maintenance
     let head_1 = yield that.dal.bindexDAL.head(1);
     if (head_1) {
@@ -352,7 +355,7 @@ function Server (home, memoryOnly, overrideConf) {
 
   this.writeRaw = (raw, type) => co(function *() {
     const parser = documentsMapping[type] && documentsMapping[type].parser;
-    const obj = parser.syncWrite(raw);
+    const obj = parser.syncWrite(raw, logger);
     return yield that.singleWritePromise(obj);
   });
 
diff --git a/test/dal/dal.js b/test/dal/dal.js
index 9415f79c5..e31968f1e 100644
--- a/test/dal/dal.js
+++ b/test/dal/dal.js
@@ -103,7 +103,7 @@ describe("DAL", function(){
   it('should have DB version 21', () => co(function *() {
     let version = yield fileDAL.getDBVersion();
     should.exist(version);
-    version.should.equal(21);
+    version.should.equal(22);
   }));
 
   it('should have no peer in a first time', function(){
diff --git a/test/integration/membership_chainability.js b/test/integration/membership_chainability.js
new file mode 100644
index 000000000..62aa5e99c
--- /dev/null
+++ b/test/integration/membership_chainability.js
@@ -0,0 +1,93 @@
+"use strict"
+
+const co        = require('co')
+const should    = require('should')
+const toolbox = require('./tools/toolbox')
+
+describe("Membership chainability", function() {
+
+  describe("before July 2017", () => {
+
+    const now = 1482220000
+    let s1, cat
+
+    const conf = {
+      msPeriod: 20,
+      nbCores: 1,
+      msValidity: 10000,
+      udTime0: now,
+      udReevalTime0: now,
+      sigQty: 1,
+      medianTimeBlocks: 1 // The medianTime always equals previous block's medianTime
+    }
+
+    before(() => co(function*() {
+      const res1 = yield toolbox.simpleNodeWith2Users(conf)
+      s1 = res1.s1
+      cat = res1.cat
+      yield s1.commit({ time: now })
+      yield s1.commit({ time: now })
+      yield s1.commit({ time: now, actives: [
+        'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd:QA2gKg6x2PhqMyKhi3hWBXuRJuRwd8G6WGHGNZIEicUR2kjE8Y3WScLyaMNQAZF3s7ewvUvpWkewopd5ugr+Bg==:1-4A21CEA1EA7C3BB0A22DEC87C5AECB38E69DB70A269CEC3644B8149B322C7669:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:cat'
+      ]})
+    }))
+
+    it('current should be the 2nd', () => s1.expect('/blockchain/current', (res) => {
+      res.should.have.property('number').equal(2)
+      res.should.have.property('actives').length(1)
+    }))
+  })
+
+  describe("after July 2017", () => {
+
+    const now = 1498860000
+    let s1, cat
+
+    const conf = {
+      msPeriod: 20,
+      nbCores: 1,
+      msValidity: 10000,
+      udTime0: now,
+      udReevalTime0: now,
+      sigQty: 1,
+      medianTimeBlocks: 1 // The medianTime always equals previous block's medianTime
+    }
+
+    before(() => co(function*() {
+      const res1 = yield toolbox.simpleNodeWith2Users(conf)
+      s1 = res1.s1
+      cat = res1.cat
+      yield s1.commit({ time: now })
+      yield s1.commit({ time: now + 20 })
+    }))
+
+    it('should refuse a block with a too early membership in it', () => co(function*() {
+      yield toolbox.shouldFail(s1.commit({
+        time: now + 20,
+        actives: ['HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd:H2jum4LLenc/69vZAFw2OppLxVQgNtp+7XL+M9nSvAGjxMf8jBEAeQ/nrfDP3Lrk2SvDvp5Hice5jFboHVdxAQ==:1-2989DEFA8BD18F111B3686EB14ED91EE7C509C9D74EE5C96AECBD4F3CA5E0FB6:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:cat']
+      }), '500 - "{\\n  \\"ucode\\": 1002,\\n  \\"message\\": \\"ruleMembershipPeriod\\"\\n}"')
+    }))
+
+    it('should not be able to renew immediately', () => co(function*() {
+      yield cat.join()
+      yield s1.commit({ time: now + 20 })
+      yield s1.expect('/blockchain/block/2', (res) => {
+        res.should.have.property('number').equal(2)
+        res.should.have.property('joiners').length(0)
+      })
+    }))
+
+    it('should be able to renew after 20 sec', () => co(function*() {
+      yield s1.commit({ time: now + 20 })
+      yield s1.expect('/blockchain/block/3', (res) => {
+        res.should.have.property('number').equal(3)
+        res.should.have.property('actives').length(1)
+      })
+    }))
+
+    it('current should be the 4th', () => s1.expect('/blockchain/current', (res) => {
+      res.should.have.property('number').equal(3)
+      res.should.have.property('actives').length(1)
+    }))
+  })
+})
diff --git a/test/integration/tools/toolbox.js b/test/integration/tools/toolbox.js
index 8a42e7fef..aadc5d3c8 100644
--- a/test/integration/tools/toolbox.js
+++ b/test/integration/tools/toolbox.js
@@ -30,9 +30,12 @@ module.exports = {
   shouldFail: (promise, message) => co(function*() {
     try {
       yield promise;
-      throw { "message": '{ "message": "Should have thrown an error" }' };
+      throw '{ "message": "Should have thrown an error" }'
     } catch(e) {
-      const err = JSON.parse(e)
+      let err = e
+      if (typeof e === "string") {
+        err = JSON.parse(e)
+      }
       err.should.have.property('message').equal(message);
     }
   }),
diff --git a/yarn.lock b/yarn.lock
index a352398a8..90f8d17a5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -87,8 +87,8 @@ ansi@^0.3.0, ansi@~0.3.1:
   resolved "https://registry.yarnpkg.com/ansi/-/ansi-0.3.1.tgz#0c42d4fb17160d5a9af1e484bace1c66922c1b21"
 
 aproba@^1.0.3:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.1.1.tgz#95d3600f07710aa0e9298c726ad5ecf2eacbabab"
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.1.2.tgz#45c6629094de4e96f693ef7eab74ae079c240fc1"
 
 archiver-utils@^1.3.0:
   version "1.3.0"
@@ -229,6 +229,12 @@ base-x@^2.0.1:
   dependencies:
     safe-buffer "^5.0.1"
 
+base-x@^3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.2.tgz#bf873861b7514279b7969f340929eab87c11d130"
+  dependencies:
+    safe-buffer "^5.0.1"
+
 basic-auth@~1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-1.1.0.tgz#45221ee429f7ee1e5035be3f51533f1cdfd29884"
@@ -312,17 +318,23 @@ brace-expansion@^1.0.0, brace-expansion@^1.1.7:
     balanced-match "^0.4.1"
     concat-map "0.0.1"
 
-bs58@4.0.0, bs58@^4.0.0:
+bs58@4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.0.tgz#65f5deaf6d74e6135a99f763ca6209ab424b9172"
   dependencies:
     base-x "^2.0.1"
 
+bs58@^4.0.0:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a"
+  dependencies:
+    base-x "^3.0.2"
+
 buffer-crc32@^0.2.1:
   version "0.2.13"
   resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
 
-buffer-shims@^1.0.0, buffer-shims@~1.0.0:
+buffer-shims@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51"
 
@@ -739,9 +751,9 @@ duniter-bma@1.3.x:
     underscore "1.8.3"
     ws "1.1.1"
 
-duniter-common@1.3.x, duniter-common@^1.3.0:
-  version "1.3.4"
-  resolved "https://registry.yarnpkg.com/duniter-common/-/duniter-common-1.3.4.tgz#5f666930d373447ec202207b89a7803581cb8b1d"
+duniter-common@1.3.x, duniter-common@^1.3.0, duniter-common@^1.3.5:
+  version "1.3.5"
+  resolved "https://registry.yarnpkg.com/duniter-common/-/duniter-common-1.3.5.tgz#b727117a2c9463d0486b7c0feb845df60b65e247"
   dependencies:
     bs58 "^4.0.0"
     co "4.6.0"
@@ -781,9 +793,9 @@ duniter-keypair@1.3.X, duniter-keypair@1.3.x:
     tweetnacl "0.14.5"
     tweetnacl-util "0.15.0"
 
-duniter-prover@1.3.x:
-  version "1.3.2"
-  resolved "https://registry.yarnpkg.com/duniter-prover/-/duniter-prover-1.3.2.tgz#8923ece7619a1170d2f4f43ee3cadac65999958e"
+duniter-prover@^1.3.3:
+  version "1.3.3"
+  resolved "https://registry.yarnpkg.com/duniter-prover/-/duniter-prover-1.3.3.tgz#2575119c7996b16bde3ad0032d248ce179080064"
   dependencies:
     async "2.2.0"
     co "4.6.0"
@@ -857,8 +869,8 @@ errorhandler@1.5.0:
     escape-html "~1.0.3"
 
 es5-ext@^0.10.14, es5-ext@^0.10.9, es5-ext@~0.10.14:
-  version "0.10.21"
-  resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.21.tgz#19a725f9e51d0300bbc1e8e821109fd9daf55925"
+  version "0.10.22"
+  resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.22.tgz#1876c51f990769c112c781ea3ebe89f84fd39071"
   dependencies:
     es6-iterator "2"
     es6-symbol "~3.1"
@@ -912,11 +924,11 @@ escape-html@~1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
 
-escape-string-regexp@1.0.2, escape-string-regexp@^1.0.2:
+escape-string-regexp@1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz#4dbc2fe674e71949caf3fb2695ce7f2dc1d9a8d1"
 
-escape-string-regexp@^1.0.5:
+escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
 
@@ -2483,14 +2495,14 @@ readable-stream@1.1.x:
     string_decoder "~0.10.x"
 
 readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.2.2:
-  version "2.2.9"
-  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.9.tgz#cf78ec6f4a6d1eb43d26488cac97f042e74b7fc8"
+  version "2.2.10"
+  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.10.tgz#effe72bb7c884c0dd335e2379d526196d9d011ee"
   dependencies:
-    buffer-shims "~1.0.0"
     core-util-is "~1.0.0"
     inherits "~2.0.1"
     isarray "~1.0.0"
     process-nextick-args "~1.0.6"
+    safe-buffer "^5.0.1"
     string_decoder "~1.0.0"
     util-deprecate "~1.0.1"
 
@@ -2689,8 +2701,8 @@ rx@^4.1.0:
   resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782"
 
 safe-buffer@^5.0.1:
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7"
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.0.tgz#fe4c8460397f9eaaaa58e73be46273408a45e223"
 
 sax@>=0.1.1:
   version "1.2.2"
@@ -3163,8 +3175,8 @@ typedarray@^0.0.6:
   resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
 
 uglify-js@^2.6:
-  version "2.8.27"
-  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.27.tgz#47787f912b0f242e5b984343be8e35e95f694c9c"
+  version "2.8.28"
+  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.28.tgz#e335032df9bb20dcb918f164589d5af47f38834a"
   dependencies:
     source-map "~0.5.1"
     yargs "~3.10.0"
-- 
GitLab