From bcc4368183a7c212f84af0b29ed87c5dac113ee2 Mon Sep 17 00:00:00 2001 From: Kot Andreev Date: Thu, 18 Apr 2019 19:53:13 +0700 Subject: [PATCH 1/3] Add sort txns --- node/mempool.js | 48 +++++++++++++++++++++++++++++++++++++++---- tests/mempool.spec.js | 33 +++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/node/mempool.js b/node/mempool.js index 84122b45..d7f52005 100644 --- a/node/mempool.js +++ b/node/mempool.js @@ -2,7 +2,7 @@ const typeforce = require('typeforce'); const debugLib = require('debug'); -const { sleep } = require('../utils'); +const {sleep} = require('../utils'); const types = require('../types'); const Tick = require('tick-tock'); @@ -10,7 +10,7 @@ const debug = debugLib('mempool:'); // TODO: add tx expiration (14 days?) -module.exports = ({ Constants, Transaction }) => +module.exports = ({Constants, Transaction}) => class Mempool { constructor(options) { this._mapTxns = new Map(); @@ -77,7 +77,7 @@ module.exports = ({ Constants, Transaction }) => const strHash = tx.hash(); if (this._mapTxns.has(strHash)) throw new Error(`tx ${strHash} already in mempool`); - this._mapTxns.set(strHash, { tx, arrived: Date.now() }); + this._mapTxns.set(strHash, {tx, arrived: Date.now()}); debug(`TX ${strHash} added`); } @@ -110,6 +110,46 @@ module.exports = ({ Constants, Transaction }) => for (let r of this._mapTxns.values()) { if (r.tx.witnessGroupId === groupId) arrResult.push(r.tx); } - return arrResult; + return this._sortTxns(arrResult); + } + /** + * @param {Array} arrTxns array of tx + * @returns sorted tx array + */ + _sortTxns(arrTxns) { + let hasConflict; + do { + hasConflict = false; + for (let i in arrTxns) { + const tx = arrTxns[i]; + const c = this._findConflicts(i, tx, arrTxns) + if (c >= 0) { + arrTxns.splice(c + 1, 0, tx); + arrTxns.splice(i, 1); + hasConflict = true; + } + } + } while (hasConflict) + return arrTxns; + } + + /** + * + * @param {Number} txIndex - index of tx in arrTxns + * @param {Transaction} tx - transaction for which the conflict is checked + * @param {Array} arrTxns - array of all tx + * @returns {Number} -1 if no conflict, else max index of tx in arrTxns + * with which input tx has a conflict + */ + _findConflicts(txIndex, tx, arrTxns) { + let maxIndex = -1; + for (const input of tx.inputs) { + const index = arrTxns.findIndex(p => p.hash() == input.txHash.toString('hex')); + if (index >= 0 && index > maxIndex) maxIndex = index; + } + if (maxIndex >= 0 && txIndex < maxIndex) { + return maxIndex; + } + return -1; } }; diff --git a/tests/mempool.spec.js b/tests/mempool.spec.js index ef5226ac..4863f79f 100644 --- a/tests/mempool.spec.js +++ b/tests/mempool.spec.js @@ -168,4 +168,37 @@ describe('Mempool tests', () => { }); + it('should find sorting conflict', () => { + const mempool = new factory.Mempool(); + const tx1 = new factory.Transaction(createDummyTx()); + const tx2 = new factory.Transaction(createDummyTx()); + const tx3 = new factory.Transaction(); + tx3.addInput(tx1.hash(), 1); + tx3.addInput(tx2.hash(), 2); + mempool.addTx(tx2); + mempool.addTx(tx3); + mempool.addTx(tx1); + + const arrTxns = Array.from(mempool._mapTxns.values()).map(t => t.tx); + const conflictIdx = mempool._findConflicts(1,tx3,arrTxns); + + assert.equal(conflictIdx,2); + }); + + it('should sort txns', () => { + const mempool = new factory.Mempool(); + const tx1 = new factory.Transaction(createDummyTx()); + const tx2 = new factory.Transaction(createDummyTx()); + const tx3 = new factory.Transaction(); + tx3.addInput(tx1.hash(), 1); + tx3.addInput(tx2.hash(), 2); + mempool.addTx(tx2); + mempool.addTx(tx3); + mempool.addTx(tx1); + + const arrTxns = mempool.getFinalTxns(0); + + assert.deepEqual(arrTxns,[tx2, tx1, tx3]); + }) + }); From 66c6af07fd712263c58cd7495d43996a60860440 Mon Sep 17 00:00:00 2001 From: Kot Andreev Date: Thu, 25 Apr 2019 17:27:25 +0700 Subject: [PATCH 2/3] Update tx hierarchical order --- node/mempool.js | 75 ++++++++++++++++++++++--------------------- tests/mempool.spec.js | 22 +++---------- 2 files changed, 42 insertions(+), 55 deletions(-) diff --git a/node/mempool.js b/node/mempool.js index d7f52005..94dac720 100644 --- a/node/mempool.js +++ b/node/mempool.js @@ -5,6 +5,7 @@ const debugLib = require('debug'); const {sleep} = require('../utils'); const types = require('../types'); const Tick = require('tick-tock'); +const {Dag} = require('dagjs'); const debug = debugLib('mempool:'); @@ -15,6 +16,8 @@ module.exports = ({Constants, Transaction}) => constructor(options) { this._mapTxns = new Map(); this._tock = new Tick(this); + this._dag = new Dag(); + this._dag.testForCyclic = false; this._tock.setInterval('outdatedTimer', this.purgeOutdated.bind(this), Constants.MEMPOOL_OUTDATED_INTERVAL); } @@ -35,6 +38,7 @@ module.exports = ({Constants, Transaction}) => // TODO: think about: is it problem that TX isn't present in mempool, but present in block if (this._mapTxns.has(txHash)) { this._mapTxns.delete(txHash); + this._dag.removeVertex(txHash); } else { debug(`removeTxns: no TX ${txHash} in mempool`); } @@ -45,6 +49,7 @@ module.exports = ({Constants, Transaction}) => this._mapTxns.forEach((tx, hash) => { if (tx.arrived < Date.now() - Constants.MEMPOOL_TX_LIFETIME) { this._mapTxns.delete(hash); + this._dag.removeVertex(hash); } }) } @@ -54,6 +59,7 @@ module.exports = ({Constants, Transaction}) => let i = Math.floor(this._mapTxns.size / 3); for (let [hash, tx] of this._mapTxns) { this._mapTxns.delete(hash); + this._dag.removeVertex(hash); if (--i == 0) break; } } @@ -78,6 +84,9 @@ module.exports = ({Constants, Transaction}) => if (this._mapTxns.has(strHash)) throw new Error(`tx ${strHash} already in mempool`); this._mapTxns.set(strHash, {tx, arrived: Date.now()}); + for (const input of tx.inputs) { + this._dag.add(input.txHash.toString('hex'), strHash); + } debug(`TX ${strHash} added`); } @@ -107,49 +116,41 @@ module.exports = ({Constants, Transaction}) => // TODO: implement lock_time // TODO: implement cache if mempool becomes huge const arrResult = []; - for (let r of this._mapTxns.values()) { - if (r.tx.witnessGroupId === groupId) arrResult.push(r.tx); + for(let hash of this._sortTxns()){ + const r = this._mapTxns.get(hash); + if(r.tx.witnessGroupId == groupId) arrResult.push(r.tx); } - return this._sortTxns(arrResult); - } - /** - * @param {Array} arrTxns array of tx - * @returns sorted tx array - */ - _sortTxns(arrTxns) { - let hasConflict; - do { - hasConflict = false; - for (let i in arrTxns) { - const tx = arrTxns[i]; - const c = this._findConflicts(i, tx, arrTxns) - if (c >= 0) { - arrTxns.splice(c + 1, 0, tx); - arrTxns.splice(i, 1); - hasConflict = true; - } - } - } while (hasConflict) - return arrTxns; + return arrResult; } /** * - * @param {Number} txIndex - index of tx in arrTxns - * @param {Transaction} tx - transaction for which the conflict is checked - * @param {Array} arrTxns - array of all tx - * @returns {Number} -1 if no conflict, else max index of tx in arrTxns - * with which input tx has a conflict + * @returns sorted tx hashes array */ - _findConflicts(txIndex, tx, arrTxns) { - let maxIndex = -1; - for (const input of tx.inputs) { - const index = arrTxns.findIndex(p => p.hash() == input.txHash.toString('hex')); - if (index >= 0 && index > maxIndex) maxIndex = index; - } - if (maxIndex >= 0 && txIndex < maxIndex) { - return maxIndex; + _sortTxns() { + let level = []; + let used = [...this._dag.tips]; + let notused = [...this._mapTxns.keys()]; + while (notused.length > 0) { + level[level.length] = new Array(); + for (let i = 0; i < notused.length; i++) { + const v = notused[i] + let k = 0; + const edgesTo = Array.from(this._dag.edgesTo(v)._edges.values())[0] + if (edgesTo) k = edgesTo.length; + const edgesFrom = this._dag._edges.get(v) + if (edgesFrom) { + const fromPrevLevel = edgesFrom.filter(e => ~used.indexOf(e.from) ) + if(fromPrevLevel) k -= fromPrevLevel.length + } + if (k == 0) { + level[level.length - 1].push(v) + notused.splice(i, 1); + i--; + } + } + used.push(...level[level.length - 1]) } - return -1; + return [].concat(...level); } }; diff --git a/tests/mempool.spec.js b/tests/mempool.spec.js index 4863f79f..ebf223cd 100644 --- a/tests/mempool.spec.js +++ b/tests/mempool.spec.js @@ -31,6 +31,7 @@ describe('Mempool tests', () => { mempool.addTx(tx); assert.isOk(mempool.hasTx(tx.hash())); + assert.isOk(mempool._dag.hasVertex(tx.hash())) }); it('should FAIL add tx to mempool (already exists)', async () => { @@ -82,6 +83,9 @@ describe('Mempool tests', () => { assert.isNotOk(mempool.hasTx(tx1.hash())); assert.isNotOk(mempool.hasTx(tx2.hash())); + assert.isNotOk(mempool._dag.hasVertex(tx1.hash())) + assert.isNotOk(mempool._dag.hasVertex(tx2.hash())) + }); it('should get tx by hash', async () => { @@ -168,23 +172,6 @@ describe('Mempool tests', () => { }); - it('should find sorting conflict', () => { - const mempool = new factory.Mempool(); - const tx1 = new factory.Transaction(createDummyTx()); - const tx2 = new factory.Transaction(createDummyTx()); - const tx3 = new factory.Transaction(); - tx3.addInput(tx1.hash(), 1); - tx3.addInput(tx2.hash(), 2); - mempool.addTx(tx2); - mempool.addTx(tx3); - mempool.addTx(tx1); - - const arrTxns = Array.from(mempool._mapTxns.values()).map(t => t.tx); - const conflictIdx = mempool._findConflicts(1,tx3,arrTxns); - - assert.equal(conflictIdx,2); - }); - it('should sort txns', () => { const mempool = new factory.Mempool(); const tx1 = new factory.Transaction(createDummyTx()); @@ -197,7 +184,6 @@ describe('Mempool tests', () => { mempool.addTx(tx1); const arrTxns = mempool.getFinalTxns(0); - assert.deepEqual(arrTxns,[tx2, tx1, tx3]); }) From bf6e9e1e0c8d75644015dab8dd0ea789f4da3beb Mon Sep 17 00:00:00 2001 From: Kot Andreev Date: Sat, 27 Apr 2019 14:02:04 +0700 Subject: [PATCH 3/3] Update order function --- node/mempool.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/node/mempool.js b/node/mempool.js index 94dac720..b5c34df9 100644 --- a/node/mempool.js +++ b/node/mempool.js @@ -116,9 +116,9 @@ module.exports = ({Constants, Transaction}) => // TODO: implement lock_time // TODO: implement cache if mempool becomes huge const arrResult = []; - for(let hash of this._sortTxns()){ + for (let hash of this._sortTxns()){ const r = this._mapTxns.get(hash); - if(r.tx.witnessGroupId == groupId) arrResult.push(r.tx); + if (r.tx.witnessGroupId == groupId) arrResult.push(r.tx); } return arrResult; } @@ -136,11 +136,10 @@ module.exports = ({Constants, Transaction}) => for (let i = 0; i < notused.length; i++) { const v = notused[i] let k = 0; - const edgesTo = Array.from(this._dag.edgesTo(v)._edges.values())[0] - if (edgesTo) k = edgesTo.length; - const edgesFrom = this._dag._edges.get(v) - if (edgesFrom) { - const fromPrevLevel = edgesFrom.filter(e => ~used.indexOf(e.from) ) + const edgesTo = this._dag._edges.get(v); + if (edgesTo) { + k = edgesTo.length; + const fromPrevLevel = edgesTo.filter(e => ~used.indexOf(e.from)); if(fromPrevLevel) k -= fromPrevLevel.length } if (k == 0) {