From c2e1e4bfc92da07c170314b6df88a544e07fdd4e Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 22 Sep 2016 23:58:19 -0700 Subject: [PATCH] refactor: chain. txdb. wallet. --- bench/walletdb.js | 6 +-- etc/sample.conf | 1 - lib/chain/chain.js | 69 ++++++++++++++------------------- lib/chain/chainentry.js | 16 +++++++- lib/db/lowlevelup.js | 23 +++++++---- lib/miner/miner.js | 3 +- lib/net/peer.js | 20 ++++------ lib/node/fullnode.js | 3 +- lib/primitives/mtx.js | 6 +++ lib/wallet/account.js | 4 +- lib/wallet/txdb.js | 86 ++++++++++++++++++++++++----------------- lib/wallet/wallet.js | 59 ++++++---------------------- lib/wallet/walletdb.js | 27 ++++++------- 13 files changed, 152 insertions(+), 171 deletions(-) diff --git a/bench/walletdb.js b/bench/walletdb.js index d4d7f6a0..b0646e0e 100644 --- a/bench/walletdb.js +++ b/bench/walletdb.js @@ -117,8 +117,4 @@ var runBench = co(function* runBench() { end(1); }); -runBench().then(process.exit).catch(function(err) { - utils.nextTick(function() { - throw err; - }); -}); +runBench().then(process.exit); diff --git a/etc/sample.conf b/etc/sample.conf index e1c3d017..444087e5 100644 --- a/etc/sample.conf +++ b/etc/sample.conf @@ -52,7 +52,6 @@ known-peers: ./known-peers # Miner # payout-address: 1111111111111111111114oLvT2 # coinbase-flags: mined by bcoin -# parallel: false # HTTP # ssl-cert: @/ssl/cert.crt diff --git a/lib/chain/chain.js b/lib/chain/chain.js index 4d273979..252450f3 100644 --- a/lib/chain/chain.js +++ b/lib/chain/chain.js @@ -1008,7 +1008,9 @@ Chain.prototype.isBusy = function isBusy() { Chain.prototype.add = co(function* add(block) { var unlock = yield this.locker.lock(block); + this.currentBlock = block.hash('hex'); + try { return yield this._add(block); } finally { @@ -1232,7 +1234,7 @@ Chain.prototype._add = co(function* add(block) { * @returns {Boolean} */ -Chain.prototype._isSlow = function _isSlow() { +Chain.prototype.isSlow = function isSlow() { if (this.options.spv) return false; @@ -1265,7 +1267,7 @@ Chain.prototype.finish = function finish(block, entry) { // Keep track of total blocks handled. this.total += 1; - if (!this._isSlow()) + if (!this.isSlow()) return; // Report memory for debugging. @@ -1777,8 +1779,7 @@ Chain.prototype.getState = co(function* getState(prev, id) { var timeStart, timeTimeout, compute, height; var i, entry, count, state, block, medianTime; - if (!deployment) - return constants.thresholdStates.FAILED; + assert(deployment); timeStart = deployment.startTime; timeTimeout = deployment.timeout; @@ -1791,11 +1792,10 @@ Chain.prototype.getState = co(function* getState(prev, id) { height = prev.height - ((prev.height + 1) % period); prev = yield prev.getAncestorByHeight(height); - if (!prev) - return constants.thresholdStates.FAILED; - - assert(prev.height === height); - assert(((prev.height + 1) % period) === 0); + if (prev) { + assert(prev.height === height); + assert(((prev.height + 1) % period) === 0); + } } entry = prev; @@ -1811,6 +1811,7 @@ Chain.prototype.getState = co(function* getState(prev, id) { if (medianTime < timeStart) { state = constants.thresholdStates.DEFINED; + stateCache[entry.hash] = state; break; } @@ -1830,58 +1831,50 @@ Chain.prototype.getState = co(function* getState(prev, id) { if (medianTime >= timeTimeout) { state = constants.thresholdStates.FAILED; - stateCache[entry.hash] = state; - continue; + break; } if (medianTime >= timeStart) { state = constants.thresholdStates.STARTED; - stateCache[entry.hash] = state; - continue; + break; } - stateCache[entry.hash] = state; - continue; + break; case constants.thresholdStates.STARTED: medianTime = yield entry.getMedianTimeAsync(); if (medianTime >= timeTimeout) { state = constants.thresholdStates.FAILED; - stateCache[entry.hash] = state; break; } - i = 0; - count = 0; block = entry; + count = 0; - while (block) { - if (i++ >= period) - break; - - if (hasBit(block, deployment)) + for (i = 0; i < period; i++) { + if (block.hasBit(deployment)) count++; block = yield block.getPrevious(); + assert(block); } if (count >= threshold) state = constants.thresholdStates.LOCKED_IN; - stateCache[entry.hash] = state; break; case constants.thresholdStates.LOCKED_IN: state = constants.thresholdStates.ACTIVE; - stateCache[entry.hash] = state; break; case constants.thresholdStates.FAILED: case constants.thresholdStates.ACTIVE: - stateCache[entry.hash] = state; break; default: assert(false, 'Bad state.'); break; } + + stateCache[entry.hash] = state; } return state; @@ -1984,7 +1977,7 @@ Chain.prototype.getLocks = co(function* getLocks(prev, tx, flags) { var i, input, entry; if (tx.isCoinbase() || tx.version < 2 || !hasFlag) - return [minHeight, minTime]; + return new LockTimes(minHeight, minTime); for (i = 0; i < tx.inputs.length; i++) { input = tx.inputs[i]; @@ -1992,9 +1985,9 @@ Chain.prototype.getLocks = co(function* getLocks(prev, tx, flags) { if (input.sequence & disableFlag) continue; - coinHeight = input.coin.height === -1 - ? this.height + 1 - : input.coin.height; + coinHeight = input.coin.height !== -1 + ? input.coin.height + : this.height + 1; if ((input.sequence & typeFlag) === 0) { coinHeight += (input.sequence & mask) - 1; @@ -2010,7 +2003,7 @@ Chain.prototype.getLocks = co(function* getLocks(prev, tx, flags) { minTime = Math.max(minTime, coinTime); } - return [minHeight, minTime]; + return new LockTimes(minHeight, minTime); }); /** @@ -2048,9 +2041,7 @@ Chain.prototype.evalLocks = co(function* evalLocks(prev, minHeight, minTime) { Chain.prototype.checkLocks = co(function* checkLocks(prev, tx, flags) { var times = yield this.getLocks(prev, tx, flags); - var minHeight = times[0]; - var minTime = times[1]; - return yield this.evalLocks(prev, minHeight, minTime); + return yield this.evalLocks(prev, times.height, times.time); }); /** @@ -2135,14 +2126,12 @@ DeploymentState.prototype.hasWitness = function hasWitness() { }; /* - * Helpers + * LockTimes */ -function hasBit(entry, deployment) { - var bits = entry.version & constants.versionbits.TOP_MASK; - var topBits = constants.versionbits.TOP_BITS; - var mask = 1 << deployment.bit; - return bits === topBits && (entry.version & mask) !== 0; +function LockTimes(height, time) { + this.height = height; + this.time = time; } /* diff --git a/lib/chain/chainentry.js b/lib/chain/chainentry.js index c8a588d5..232931e5 100644 --- a/lib/chain/chainentry.js +++ b/lib/chain/chainentry.js @@ -417,7 +417,8 @@ ChainEntry.prototype.isSuperMajorityAsync = co(function* isSuperMajorityAsync(ve }); /** - * Test whether the entry is potentially an ancestor of a checkpoint. + * Test whether the entry is potentially + * an ancestor of a checkpoint. * @returns {Boolean} */ @@ -429,6 +430,19 @@ ChainEntry.prototype.isHistorical = function isHistorical() { return false; }; +/** + * Test whether the entry contains a version bit. + * @param {Object} deployment + * @returns {Boolean} + */ + +ChainEntry.prototype.hasBit = function hasBit(deployment) { + var bits = this.version & constants.versionbits.TOP_MASK; + var topBits = constants.versionbits.TOP_BITS; + var mask = 1 << deployment.bit; + return bits === topBits && (this.version & mask) !== 0; +}; + ChainEntry.prototype.__defineGetter__('rhash', function() { return utils.revHex(this.hash); }); diff --git a/lib/db/lowlevelup.js b/lib/db/lowlevelup.js index d48fb5ab..152e4b01 100644 --- a/lib/db/lowlevelup.js +++ b/lib/db/lowlevelup.js @@ -304,21 +304,23 @@ LowlevelUp.prototype.has = co(function* has(key) { LowlevelUp.prototype.iterate = co(function* iterate(options) { var items = []; - var iter, kv, result; + var parse = options.parse; + var iter, result, data; - assert(typeof options.parse === 'function', 'Parse must be a function.'); + assert(typeof parse === 'function', 'Parse must be a function.'); iter = this.iterator(options); for (;;) { - kv = yield iter.next(); - if (!kv) + result = yield iter.next(); + + if (!result) return items; - result = options.parse(kv[0], kv[1]); + data = parse(result.key, result.value); - if (result) - items.push(result); + if (data) + items.push(data); } return items; @@ -453,7 +455,7 @@ Iterator.prototype.next = function() { return; } - resolve([key, value]); + resolve(new KeyValue(key, value)); }); }); }; @@ -473,6 +475,11 @@ Iterator.prototype.end = function end() { * Helpers */ +function KeyValue(key, value) { + this.key = key; + this.value = value; +} + function isNotFound(err) { if (!err) return false; diff --git a/lib/miner/miner.js b/lib/miner/miner.js index e07ea457..642d691c 100644 --- a/lib/miner/miner.js +++ b/lib/miner/miner.js @@ -115,7 +115,7 @@ Miner.prototype._init = function _init() { if (bcoin.useWorkers) { this.workerPool = new bcoin.workers({ - size: this.options.parallel ? 2 : 1, + size: 1, timeout: -1 }); @@ -268,7 +268,6 @@ Miner.prototype.createBlock = co(function* createBlock(tip) { address: this.address, coinbaseFlags: this.coinbaseFlags, witness: this.chain.segwitActive, - parallel: this.options.parallel, network: this.network }); diff --git a/lib/net/peer.js b/lib/net/peer.js index 36b7e934..d3b0c7c5 100644 --- a/lib/net/peer.js +++ b/lib/net/peer.js @@ -339,6 +339,13 @@ Peer.prototype._bip151 = co(function* _bip151() { } catch (err) { this.error(err, true); } + + assert(this.bip151.completed); + + if (this.bip151.handshake) { + this.logger.info('BIP151 handshake complete (%s).', this.hostname); + this.logger.info('Connection is encrypted (%s).', this.hostname); + } }); /** @@ -347,17 +354,7 @@ Peer.prototype._bip151 = co(function* _bip151() { */ Peer.prototype._bip150 = co(function* _bip150() { - if (!this.bip151) - return; - - assert(this.bip151.completed); - - if (this.bip151.handshake) { - this.logger.info('BIP151 handshake complete (%s).', this.hostname); - this.logger.info('Connection is encrypted (%s).', this.hostname); - } - - if (!this.bip150) + if (!this.bip151 || !this.bip150) return; assert(!this.bip150.completed); @@ -2090,7 +2087,6 @@ Peer.prototype._handleCmpctBlock = co(function* _handleCmpctBlock(packet) { return; } - // Sort of a lock too. this.compactBlocks[hash] = block; result = block.fillMempool(this.mempool); diff --git a/lib/node/fullnode.js b/lib/node/fullnode.js index 62d0ab9c..84a79212 100644 --- a/lib/node/fullnode.js +++ b/lib/node/fullnode.js @@ -124,8 +124,7 @@ function Fullnode(options) { mempool: this.mempool, fees: this.fees, address: this.options.payoutAddress, - coinbaseFlags: this.options.coinbaseFlags, - parallel: this.options.parallel + coinbaseFlags: this.options.coinbaseFlags }); // Wallet database needs access to fees. diff --git a/lib/primitives/mtx.js b/lib/primitives/mtx.js index b1206558..0f8386a2 100644 --- a/lib/primitives/mtx.js +++ b/lib/primitives/mtx.js @@ -844,6 +844,12 @@ MTX.prototype.template = function template(ring) { var total = 0; var i; + if (Array.isArray(ring)) { + for (i = 0; i < ring.length; i++) + total += this.template(ring[i]); + return total; + } + for (i = 0; i < this.inputs.length; i++) { if (!ring.ownInput(this, i)) continue; diff --git a/lib/wallet/account.js b/lib/wallet/account.js index 754d06a9..6e1a83ae 100644 --- a/lib/wallet/account.js +++ b/lib/wallet/account.js @@ -592,13 +592,13 @@ Account.prototype.setDepth = co(function* setDepth(receiveDepth, changeDepth) { } if (rings.length === 0) - return []; + return; yield this.saveAddress(rings); this.save(); - return [receive, change]; + return receive; }); /** diff --git a/lib/wallet/txdb.js b/lib/wallet/txdb.js index 9d330cb3..061cb3c0 100644 --- a/lib/wallet/txdb.js +++ b/lib/wallet/txdb.js @@ -380,19 +380,19 @@ TXDB.prototype.getInfo = function getInfo(tx) { * to orphan list. Stored by its required coin ID. * @private * @param {Outpoint} prevout - Required coin hash & index. - * @param {Buffer} spender - Spender input hash and index. + * @param {Buffer} input - Spender input hash and index. * @param {Function} callback - Returns [Error, Buffer]. */ -TXDB.prototype.addOrphan = co(function* addOrphan(prevout, spender) { - var p = new BufferWriter(); +TXDB.prototype.addOrphan = co(function* addOrphan(prevout, input) { var key = layout.o(prevout.hash, prevout.index); var data = yield this.get(key); + var p = new BufferWriter(); if (data) p.writeBytes(data); - p.writeBytes(spender); + p.writeBytes(input); this.put(key, p.render()); }); @@ -406,24 +406,24 @@ TXDB.prototype.addOrphan = co(function* addOrphan(prevout, spender) { */ TXDB.prototype.getOrphans = co(function* getOrphans(hash, index) { + var key = layout.o(hash, index); + var data = yield this.get(key); var items = []; - var i, data, orphans, orphan, tx, p; - - data = yield this.get(layout.o(hash, index)); + var i, inputs, input, tx, p; if (!data) return; p = new BufferReader(data); - orphans = []; + inputs = []; while (p.left()) - orphans.push(bcoin.outpoint.fromRaw(p)); + inputs.push(bcoin.outpoint.fromRaw(p)); - for (i = 0; i < orphans.length; i++) { - orphan = orphans[i]; - tx = yield this.getTX(orphan.hash); - items.push([orphan, tx]); + for (i = 0; i < inputs.length; i++) { + input = inputs[i]; + tx = yield this.getTX(input.hash); + items.push(new Orphan(input, tx)); } return items; @@ -439,7 +439,7 @@ TXDB.prototype.getOrphans = co(function* getOrphans(hash, index) { */ TXDB.prototype.verify = co(function* verify(tx, info) { - var i, input, prevout, address, coin, spent, rtx, rinfo, result; + var i, input, prevout, address, coin, spent, conflict; for (i = 0; i < tx.inputs.length; i++) { input = tx.inputs[i]; @@ -462,7 +462,7 @@ TXDB.prototype.verify = co(function* verify(tx, info) { // Skip invalid transactions if (this.options.verify) { - if (!tx.verifyInput(i)) + if (!(yield tx.verifyInputAsync(i))) return false; } @@ -487,25 +487,23 @@ TXDB.prototype.verify = co(function* verify(tx, info) { // Skip invalid transactions if (this.options.verify) { - if (!tx.verifyInput(i)) + if (!(yield tx.verifyInputAsync(i))) return false; } this.logger.warning('Removing conflicting tx: %s.', utils.revHex(spent.hash)); - result = yield this.removeConflict(spent.hash, tx); + // Remove the older double spender. + conflict = yield this.removeConflict(spent.hash, tx); // Spender was not removed, the current // transaction is not elligible to be added. - if (!result) + if (!conflict) return false; - rtx = result[0]; - rinfo = result[1]; - // Emit the _removed_ transaction. - this.emit('conflict', rtx, rinfo); + this.emit('conflict', conflict.tx, conflict.info); } return true; @@ -521,7 +519,7 @@ TXDB.prototype.verify = co(function* verify(tx, info) { TXDB.prototype.resolveOrphans = co(function* resolveOrphans(tx, index) { var hash = tx.hash('hex'); - var i, orphans, coin, item, input, orphan; + var i, orphans, coin, input, orphan, key; orphans = yield this.getOrphans(hash, index); @@ -534,27 +532,28 @@ TXDB.prototype.resolveOrphans = co(function* resolveOrphans(tx, index) { // Add input to orphan for (i = 0; i < orphans.length; i++) { - item = orphans[i]; - input = item[0]; - orphan = item[1]; + orphan = orphans[i]; + input = orphan.input; + tx = orphan.tx; // Probably removed by some other means. - if (!orphan) + if (!tx) continue; - orphan.inputs[input.index].coin = coin; + tx.inputs[input.index].coin = coin; - assert(orphan.inputs[input.index].prevout.hash === hash); - assert(orphan.inputs[input.index].prevout.index === index); + assert(tx.inputs[input.index].prevout.hash === hash); + assert(tx.inputs[input.index].prevout.index === index); // Verify that input script is correct, if not - add // output to unspent and remove orphan from storage - if (!this.options.verify || (yield orphan.verifyInputAsync(input.index))) { - this.put(layout.d(input.hash, input.index), coin.toRaw()); + if (!this.options.verify || (yield tx.verifyInputAsync(input.index))) { + key = layout.d(input.hash, input.index); + this.put(key, coin.toRaw()); return true; } - yield this.lazyRemove(orphan); + yield this.lazyRemove(tx); } // Just going to be added again outside. @@ -621,11 +620,14 @@ TXDB.prototype._add = co(function* add(tx, info) { for (i = 0; i < info.accounts.length; i++) { account = info.accounts[i]; + this.put(layout.T(account, hash), DUMMY); + if (tx.ts === 0) this.put(layout.P(account, hash), DUMMY); else this.put(layout.H(account, tx.height, hash), DUMMY); + this.put(layout.M(account, tx.ps, hash), DUMMY); } @@ -646,7 +648,7 @@ TXDB.prototype._add = co(function* add(tx, info) { key = prevout.hash + prevout.index; - // s/[outpoint-key] -> [spender-hash]|[spender-input-index] + // s[outpoint-key] -> [spender-hash]|[spender-input-index] spender = bcoin.outpoint.fromTX(tx, i).toRaw(); this.put(layout.s(prevout.hash, prevout.index), spender); @@ -757,7 +759,7 @@ TXDB.prototype.removeConflict = co(function* removeConflict(hash, ref) { info = yield this.removeRecursive(tx); - return [tx, info]; + return new Conflict(tx, info); }); /** @@ -774,6 +776,7 @@ TXDB.prototype.removeRecursive = co(function* removeRecursive(tx) { for (i = 0; i < tx.outputs.length; i++) { spent = yield this.isSpent(hash, i); + if (!spent) continue; @@ -1019,11 +1022,14 @@ TXDB.prototype.__remove = co(function* remove(tx, info) { for (i = 0; i < info.accounts.length; i++) { account = info.accounts[i]; + this.del(layout.T(account, hash)); + if (tx.ts === 0) this.del(layout.P(account, hash)); else this.del(layout.H(account, tx.height, hash)); + this.del(layout.M(account, tx.ps, hash)); } @@ -2092,6 +2098,16 @@ function sortCoins(coins) { }); } +function Conflict(tx, info) { + this.tx = tx; + this.info = info; +} + +function Orphan(input, tx) { + this.input = input; + this.tx = tx; +} + /* * Expose */ diff --git a/lib/wallet/wallet.js b/lib/wallet/wallet.js index 468e8c02..321faec7 100644 --- a/lib/wallet/wallet.js +++ b/lib/wallet/wallet.js @@ -56,7 +56,6 @@ function Wallet(db, options) { this.db = db; this.network = db.network; this.logger = db.logger; - this.workerPool = db.workerPool; this.writeLock = new bcoin.locker(this); this.fundLock = new bcoin.locker(this); @@ -1220,8 +1219,7 @@ Wallet.prototype._syncOutputDepth = co(function* syncOutputDepth(info) { var receive = []; var accounts = {}; var i, j, path, paths, account; - var receiveDepth, changeDepth; - var ret, rcv, chng; + var receiveDepth, changeDepth, ring; this.start(); @@ -1265,22 +1263,19 @@ Wallet.prototype._syncOutputDepth = co(function* syncOutputDepth(info) { if (!account) continue; - ret = yield account.setDepth(receiveDepth, changeDepth); + ring = yield account.setDepth(receiveDepth, changeDepth); - rcv = ret[0]; - chng = ret[1]; - - if (rcv) - receive.push(rcv); + if (ring) + receive.push(ring); } + yield this.commit(); + if (receive.length > 0) { this.db.emit('address', this.id, receive); this.emit('address', receive); } - yield this.commit(); - return receive; }); @@ -1349,17 +1344,8 @@ Wallet.prototype.getRedeem = co(function* getRedeem(hash) { */ Wallet.prototype.template = co(function* template(tx) { - var total = 0; - var i, rings, ring; - - rings = yield this.deriveInputs(tx); - - for (i = 0; i < rings.length; i++) { - ring = rings[i]; - total += tx.template(ring); - } - - return total; + var rings = yield this.deriveInputs(tx); + return tx.template(rings); }); /** @@ -1372,7 +1358,7 @@ Wallet.prototype.template = co(function* template(tx) { */ Wallet.prototype.sign = co(function* sign(tx, options) { - var master, rings; + var rings; if (!options) options = {}; @@ -1380,36 +1366,13 @@ Wallet.prototype.sign = co(function* sign(tx, options) { if (typeof options === 'string' || Buffer.isBuffer(options)) options = { passphrase: options }; - master = yield this.unlock(options.passphrase, options.timeout); + yield this.unlock(options.passphrase, options.timeout); rings = yield this.deriveInputs(tx); - return yield this.signAsync(rings, tx); + return yield tx.signAsync(rings); }); -/** - * Sign a transaction asynchronously. - * @param {KeyRing[]} rings - * @param {MTX} tx - * @param {Function} callback - Returns [Error, Number] (total number - * of inputs scripts built and signed). - */ - -Wallet.prototype.signAsync = function signAsync(rings, tx) { - var result; - - if (!this.workerPool) { - try { - result = tx.sign(rings); - } catch (e) { - return Promise.reject(e); - } - return Promise.resolve(result); - } - - return this.workerPool.sign(tx, rings, null); -}; - /** * Fill transaction with coins (accesses db). * @param {TX} tx diff --git a/lib/wallet/walletdb.js b/lib/wallet/walletdb.js index 4690e680..2fee45ae 100644 --- a/lib/wallet/walletdb.js +++ b/lib/wallet/walletdb.js @@ -128,7 +128,6 @@ function WalletDB(options) { this.logger = options.logger || bcoin.defaultLogger; this.batches = {}; this.wallets = {}; - this.workerPool = null; this.tip = this.network.genesis.hash; this.height = 0; @@ -163,9 +162,6 @@ function WalletDB(options) { bufferKeys: !utils.isBrowser }); - if (bcoin.useWorkers) - this.workerPool = new bcoin.workers(); - this._init(); } @@ -184,13 +180,7 @@ WalletDB.layout = layout; */ WalletDB.prototype._init = function _init() { - var self = this; - - if (bcoin.useWorkers) { - this.workerPool.on('error', function(err) { - self.emit('error', err); - }); - } + ; }; /** @@ -249,7 +239,7 @@ WalletDB.prototype.backup = function backup(path) { */ WalletDB.prototype.getDepth = co(function* getDepth() { - var kv, iter, depth; + var result, iter, depth; // This may seem like a strange way to do // this, but updating a global state when @@ -266,14 +256,14 @@ WalletDB.prototype.getDepth = co(function* getDepth() { reverse: true }); - kv = yield iter.next(); + result = yield iter.next(); - if (!kv) + if (!result) return 1; yield iter.end(); - depth = layout.ww(kv[0]); + depth = layout.ww(result.key); return depth + 1; }); @@ -1546,11 +1536,15 @@ PathInfo.prototype.fromTX = function fromTX(tx, table) { for (i = 0; i < hashes.length; i++) { hash = hashes[i]; paths = table[hash]; + for (j = 0; j < paths.length; j++) { path = paths[j]; + if (path.wid !== this.wid) continue; + this.pathMap[hash] = path; + if (!uniq[path.account]) { uniq[path.account] = true; this.accounts.push(path.account); @@ -1563,10 +1557,13 @@ PathInfo.prototype.fromTX = function fromTX(tx, table) { for (i = 0; i < hashes.length; i++) { hash = hashes[i]; paths = table[hash]; + for (j = 0; j < paths.length; j++) { path = paths[j]; + if (path.wid !== this.wid) continue; + this.paths.push(path); } }