From f67902a9cf8c0f1126eb2ea57687d41d900e8140 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 4 Mar 2016 03:49:43 -0800 Subject: [PATCH] refactor. allow tx querying by timestamp. --- lib/bcoin/address.js | 87 +++------- lib/bcoin/http.js | 6 +- lib/bcoin/merkleblock.js | 4 + lib/bcoin/pool.js | 2 +- lib/bcoin/txdb.js | 331 +++++++++++++++++++++++---------------- lib/bcoin/wallet.js | 106 +++++++++---- lib/bcoin/walletdb.js | 96 ++++++++---- 7 files changed, 362 insertions(+), 270 deletions(-) diff --git a/lib/bcoin/address.js b/lib/bcoin/address.js index 655e78e3..89fb5c44 100644 --- a/lib/bcoin/address.js +++ b/lib/bcoin/address.js @@ -68,11 +68,7 @@ Address.prototype.getID = function getID() { Address.prototype.addKey = function addKey(key) { key = utils.ensureBuffer(key); - var has = this.keys.some(function(k) { - return utils.isEqual(k, key); - }); - - if (has) + if (utils.indexOf(this.keys, key) !== -1) return; this.keys.push(key); @@ -81,15 +77,13 @@ Address.prototype.addKey = function addKey(key) { }; Address.prototype.removeKey = function removeKey(key) { + var index; + key = utils.ensureBuffer(key); - var index = this.keys.map(function(k, i) { - return utils.isEqual(k, key) ? i : null; - }).filter(function(i) { - return i !== null; - })[0]; + index = utils.indexOf(this.keys, key); - if (index == null) + if (index === -1) return; this.keys.splice(index, 1); @@ -175,7 +169,8 @@ Address.prototype.getProgramAddress = function getProgramAddress() { if (this._programAddress) return this._programAddress; - this._programAddress = Address.compileHash(this.getProgramHash(), 'scripthash'); + this._programAddress = + Address.compileHash(this.getProgramHash(), 'scripthash'); return this._programAddress; }; @@ -215,10 +210,13 @@ Address.prototype.getScriptAddress = function getScriptAddress() { if (this._scriptAddress) return this._scriptAddress; - if (this.witness) - this._scriptAddress = Address.compileHash(this.getScriptHash256(), 'witnessscripthash'); - else - this._scriptAddress = Address.compileHash(this.getScriptHash160(), 'scripthash'); + if (this.witness) { + this._scriptAddress = + Address.compileHash(this.getScriptHash256(), 'witnessscripthash'); + } else { + this._scriptAddress = + Address.compileHash(this.getScriptHash160(), 'scripthash'); + } return this._scriptAddress; }; @@ -273,57 +271,26 @@ Address.prototype._getAddressMap = function _getAddressMap() { return this.addressMap; }; -Address.prototype.ownOutput = function ownOutput(tx, index) { - var addressMap = this._getAddressMap(); - var outputs = tx.outputs; - - if ((tx instanceof bcoin.output) || (tx instanceof bcoin.coin)) { - outputs = [tx]; - tx = null; - } - - outputs = outputs.filter(function(output, i) { - if (index != null && index !== i) - return false; - - return output.test(addressMap); - }, this); - - if (outputs.length === 0) - return false; - - return outputs; -}; - Address.prototype.ownInput = function ownInput(tx, index) { var addressMap = this._getAddressMap(); - var inputs = tx.inputs; - if (tx instanceof bcoin.input) { - inputs = [tx]; - tx = null; - } + if (tx instanceof bcoin.input) + return tx.test(addressMap); - inputs = inputs.filter(function(input, i) { - if (index != null && index !== i) - return false; + return tx.testInputs(addressMap, index); +}; - if (input.output) - return input.output.test(addressMap); +Address.prototype.ownOutput = function ownOutput(tx, index) { + var addressMap = this._getAddressMap(); - return input.test(addressMap); - }, this); + if (tx instanceof bcoin.output) + return tx.test(addressMap); - if (inputs.length === 0) - return false; - - return inputs; + return tx.testOutputs(addressMap, index); }; Address.prototype.scriptInputs = function scriptInputs(tx, index) { var self = this; - var publicKey = this.getPublicKey(); - var redeem = this.getScript(); if (index && typeof index === 'object') index = tx.inputs.indexOf(index); @@ -347,13 +314,11 @@ Address.prototype.scriptInputs = function scriptInputs(tx, index) { Address.prototype.signInputs = function signInputs(tx, type, index) { var self = this; - var key = this.key; - var total = 0; if (index && typeof index === 'object') index = tx.inputs.indexOf(index); - if (!key.privateKey) + if (!this.key.privateKey) return 0; return tx.inputs.reduce(function(total, input, i) { @@ -375,13 +340,11 @@ Address.prototype.signInputs = function signInputs(tx, type, index) { Address.prototype.sign = function sign(tx, type, index) { var self = this; - var redeem = this.getScript(); - var key = this.key; if (index && typeof index === 'object') index = tx.inputs.indexOf(index); - if (!key.privateKey) + if (!this.key.privateKey) return 0; // Add signature script to each input diff --git a/lib/bcoin/http.js b/lib/bcoin/http.js index 34d036ba..e77d1e04 100644 --- a/lib/bcoin/http.js +++ b/lib/bcoin/http.js @@ -201,9 +201,9 @@ HTTPServer.prototype._init = function _init() { // Update wallet / sync address depth this.put('/wallet/:id', function(req, res, next, send) { var id = req.params.id; - var receive = req.body.receiveDepth; - var change = req.body.changeDepth; - self.node.walletdb.syncDepth(id, receive, change, function(err) { + var receive = req.body.receiveDepth >>> 0; + var change = req.body.changeDepth >>> 0; + self.node.walletdb.setDepth(id, receive, change, function(err) { if (err) return next(err); diff --git a/lib/bcoin/merkleblock.js b/lib/bcoin/merkleblock.js index 5c1f48d3..161e14ce 100644 --- a/lib/bcoin/merkleblock.js +++ b/lib/bcoin/merkleblock.js @@ -117,6 +117,10 @@ MerkleBlock.prototype._verify = function _verify() { return true; }; +MerkleBlock.prototype.getCoinbaseHeight = function getCoinbaseHeight() { + return -1; +}; + MerkleBlock.prototype.inspect = function inspect() { var copy = bcoin.merkleblock(this); copy.__proto__ = null; diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index b80c6d8f..0b8d2362 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -1282,7 +1282,7 @@ Pool.prototype.searchWallet = function(wallet, callback) { if (!this.options.spv) return callback(); - wallet.getLast(function(err, ts, height) { + wallet.getLastTime(function(err, ts, height) { if (err) return callback(err); diff --git a/lib/bcoin/txdb.js b/lib/bcoin/txdb.js index c5ca6a32..e46aa659 100644 --- a/lib/bcoin/txdb.js +++ b/lib/bcoin/txdb.js @@ -76,7 +76,9 @@ TXPool.prototype._lock = function _lock(func, args, force) { TXPool.prototype.getMap = function getMap(tx, callback) { var self = this; - var addresses = tx.getAddresses(); + var input = tx.getInputAddresses(); + var output = tx.getOutputAddresses(); + var addresses = utils.uniqs(input.concat(output)); var map; function cb(err, map) { @@ -87,12 +89,12 @@ TXPool.prototype.getMap = function getMap(tx, callback) { map.output = []; map.all = []; - tx.getInputAddresses().forEach(function(address) { + input.forEach(function(address) { assert(map[address]); map.input = map.input.concat(map[address]); }); - tx.getOutputAddresses().forEach(function(address) { + output.forEach(function(address) { assert(map[address]); map.output = map.output.concat(map[address]); }); @@ -122,7 +124,6 @@ TXPool.prototype.mapAddresses = function mapAddresses(address, callback) { var iter; if (Array.isArray(address)) { - address = utils.uniqs(address); return utils.forEachSerial(address, function(address, next) { self.mapAddresses(address, function(err, res) { if (err) @@ -166,7 +167,6 @@ TXPool.prototype.mapAddresses = function mapAddresses(address, callback) { return iter.end(function(err) { if (err) return callback(err); - map[address] = utils.uniqs(map[address]); return callback(null, map); }); } @@ -261,6 +261,13 @@ TXPool.prototype.add = function add(tx, callback) { }); }; +function pad32(num) { + num = num + ''; + while (num.length < 10) + num = '0' + num; + return num; +} + // This big scary function is what a persistent tx pool // looks like. It's a semi mempool in that it can handle // receiving txs out of order. @@ -269,10 +276,10 @@ TXPool.prototype._add = function add(tx, map, callback, force) { var prefix = this.prefix + '/'; var hash = tx.hash('hex'); var updated = false; - var own = false; - var uniq = {}; var batch; + assert(tx.ts > 0 || tx.ps > 0); + var unlock = this._lock(add, [tx, map, callback], force); if (!unlock) return; @@ -290,38 +297,29 @@ TXPool.prototype._add = function add(tx, map, callback, force) { batch = self.db.batch(); if (existing) { - own = true; // Tricky - update the tx and coin in storage, // and remove pending flag to mark as confirmed. if (existing.ts === 0 && tx.ts !== 0) { + assert(tx.height >= 0); + assert(existing.ps > 0); + batch.put(prefix + 't/t/' + hash, tx.toExtended()); - batch.put(prefix + 't/h/' + tx.height + '/' + hash, DUMMY); - batch.del(prefix + 'p/t/' + hash); + batch.put(prefix + 't/h/h/' + pad32(tx.height) + '/' + hash, DUMMY); + batch.del(prefix + 't/s/s/' + pad32(existing.ps) + '/' + hash); + batch.put(prefix + 't/s/s/' + pad32(tx.ts) + '/' + hash, DUMMY); + batch.del(prefix + 't/p/t/' + hash); - tx.inputs.forEach(function(input) { - var address = input.getAddress(); - - if (input.isCoinbase()) - return; - - if (address && !uniq[address]) { - uniq[address] = true; - map[address].forEach(function(id) { - batch.del(prefix + 'p/a/' + id + '/' + hash); - }); - } + map.all.forEach(function(id) { + batch.put( + prefix + 't/h/a/' + id + '/' + pad32(tx.height) + '/' + hash, DUMMY); + batch.del( + prefix + 't/s/a/' + id + '/' + pad32(existing.ps) + '/' + hash); + batch.put( + prefix + 't/s/a/' + id + '/' + pad32(tx.ts) + '/' + hash, DUMMY); + batch.del(prefix + 't/p/a/' + id + '/' + hash); }); utils.forEachSerial(tx.outputs, function(output, next, i) { - var address = output.getAddress(); - - if (address && !uniq[address]) { - uniq[address] = true; - map[address].forEach(function(id) { - batch.del(prefix + 'p/a/' + id + '/' + hash); - }); - } - self.getCoin(hash, i, function(err, coin) { if (err) return next(err); @@ -355,6 +353,31 @@ TXPool.prototype._add = function add(tx, map, callback, force) { return done(null, false); } + batch.put(prefix + 't/t/' + hash, tx.toExtended()); + + if (tx.ts === 0) { + assert(tx.ps > 0); + batch.put(prefix + 't/p/t/' + hash, DUMMY); + batch.put(prefix + 't/s/s/' + pad32(tx.ps) + '/' + hash, DUMMY); + } else { + batch.put(prefix + 't/h/h/' + pad32(tx.height) + '/' + hash, DUMMY); + batch.put(prefix + 't/s/s/' + pad32(tx.ts) + '/' + hash, DUMMY); + } + + map.all.forEach(function(id) { + batch.put(prefix + 't/a/' + id + '/' + hash, DUMMY); + if (tx.ts === 0) { + batch.put(prefix + 't/p/a/' + id + '/' + hash, DUMMY); + batch.put( + prefix + 't/s/a/' + id + '/' + pad32(tx.ps) + '/' + hash, DUMMY); + } else { + batch.put( + prefix + 't/h/a/' + id + '/' + pad32(tx.height) + '/' + hash, DUMMY); + batch.put( + prefix + 't/s/a/' + id + '/' + pad32(tx.ts) + '/' + hash, DUMMY); + } + }); + // Consume unspent money or add orphans utils.forEachSerial(tx.inputs, function(input, next, i) { var key; @@ -372,15 +395,6 @@ TXPool.prototype._add = function add(tx, map, callback, force) { address = input.getAddress(); - if (address && !uniq[address]) { - uniq[address] = true; - map[address].forEach(function(id) { - batch.put(prefix + 't/a/' + id + '/' + hash, DUMMY); - if (tx.ts === 0) - batch.put(prefix + 'p/a/' + id + '/' + hash, DUMMY); - }); - } - if (coin) { // Add TX to inputs and spend money input.output = coin; @@ -393,7 +407,6 @@ TXPool.prototype._add = function add(tx, map, callback, force) { return done(null, false); updated = true; - own = true; if (address) { map[address].forEach(function(id) { @@ -416,8 +429,6 @@ TXPool.prototype._add = function add(tx, map, callback, force) { if (!address || !map[address].length) return next(); - own = true; - self.getTX(input.prevout.hash, function(err, result) { if (err) return done(err); @@ -452,7 +463,6 @@ TXPool.prototype._add = function add(tx, map, callback, force) { key = hash + '/' + i; coin = bcoin.coin(tx, i); - own = true; self._getOrphans(key, function(err, orphans) { var some = false; @@ -504,15 +514,6 @@ TXPool.prototype._add = function add(tx, map, callback, force) { return next(err); if (!orphans) { - if (address && !uniq[address]) { - uniq[address] = true; - map[address].forEach(function(id) { - batch.put(prefix + 't/a/' + id + '/' + hash, DUMMY); - if (tx.ts === 0) - batch.put(prefix + 'p/a/' + id + '/' + hash, DUMMY); - }); - } - if (address) { map[address].forEach(function(id) { batch.put( @@ -533,15 +534,6 @@ TXPool.prototype._add = function add(tx, map, callback, force) { if (err) return done(err); - if (!own) - return done(null, false); - - batch.put(prefix + 't/t/' + hash, tx.toExtended()); - if (tx.ts === 0) - batch.put(prefix + 'p/t/' + hash, DUMMY); - else - batch.put(prefix + 't/h/' + tx.height + '/' + hash, DUMMY); - batch.write(function(err) { if (err) return done(err); @@ -620,13 +612,27 @@ TXPool.prototype._remove = function remove(tx, map, callback) { var prefix = this.prefix + '/'; var hash = tx.hash('hex'); var batch = this.db.batch(); - var uniq = {}; batch.del(prefix + 't/t/' + hash); - if (tx.ts === 0) - batch.del(prefix + 'p/t/' + hash); - else - batch.del(prefix + 't/h/' + tx.height + '/' + hash); + + if (tx.ts === 0) { + batch.del(prefix + 't/p/t/' + hash); + batch.del(prefix + 't/s/s/' + pad32(tx.ps) + '/' + hash); + } else { + batch.del(prefix + 't/h/h/' + pad32(tx.height) + '/' + hash); + batch.del(prefix + 't/s/s/' + pad32(tx.ts) + '/' + hash); + } + + map.all.forEach(function(id) { + batch.del(prefix + 't/a/' + id + '/' + hash); + if (tx.ts === 0) { + batch.del(prefix + 't/p/a/' + id + '/' + hash); + batch.del(prefix + 't/s/a/' + id + '/' + pad32(tx.ps) + '/' + hash); + } else { + batch.del(prefix + 't/h/a/' + id + '/' + pad32(tx.height) + '/' + hash); + batch.del(prefix + 't/s/a/' + id + '/' + pad32(tx.ts) + '/' + hash); + } + }); this.fillTX(tx, function(err) { if (err) @@ -638,15 +644,6 @@ TXPool.prototype._remove = function remove(tx, map, callback) { if (input.isCoinbase()) return; - if (address && !uniq[address]) { - uniq[address] = true; - map[address].forEach(function(id) { - batch.del(prefix + 't/a/' + id + '/' + hash); - if (tx.ts === 0) - batch.del(prefix + 'p/a/' + id + '/' + hash); - }); - } - if (!input.output) return; @@ -670,15 +667,6 @@ TXPool.prototype._remove = function remove(tx, map, callback) { tx.outputs.forEach(function(output, i) { var address = output.getAddress(); - if (address && !uniq[address]) { - uniq[address] = true; - map[address].forEach(function(id) { - batch.del(prefix + 't/a/' + id + '/' + hash); - if (tx.ts === 0) - batch.del(prefix + 'p/a/' + id + '/' + hash); - }); - } - if (address) { map[address].forEach(function(id) { batch.del(prefix + 'u/a/' + id + '/' + hash + '/' + i); @@ -735,10 +723,13 @@ TXPool.prototype.unconfirm = function unconfirm(hash, callback) { TXPool.prototype._unconfirm = function unconfirm(tx, map, callback) { var self = this; var prefix = this.prefix + '/'; - var uniq = {}; var hash = tx.hash('hex'); var batch = this.db.batch(); var height = tx.height; + var ts = tx.ts; + + if (height !== -1) + return callback(null, false); tx.height = -1; tx.ps = utils.now(); @@ -747,35 +738,19 @@ TXPool.prototype._unconfirm = function unconfirm(tx, map, callback) { tx.block = null; batch.put(prefix + 't/t/' + hash, tx.toExtended()); - batch.put(prefix + 'p/t/' + hash, DUMMY); - batch.del(prefix + 't/h/' + height + '/' + hash); + batch.put(prefix + 't/p/t/' + hash, DUMMY); + batch.del(prefix + 't/h/h/' + pad32(height) + '/' + hash); + batch.del(prefix + 't/s/s/' + pad32(ts) + '/' + hash); + batch.put(prefix + 't/s/s/' + pad32(tx.ps) + '/' + hash, DUMMY); - tx.inputs.forEach(function(input) { - var address; - - if (input.isCoinbase()) - return; - - address = input.getAddress(); - - if (address && !uniq[address]) { - uniq[address] = true; - map[address].forEach(function(id) { - batch.put(prefix + 'p/a/' + id + '/' + hash, DUMMY); - }); - } + map.all.forEach(function(id) { + batch.del(prefix + 't/h/a/' + id + '/' + pad32(height) + '/' + hash); + batch.del(prefix + 't/s/a/' + id + '/' + pad32(ts) + '/' + hash); + batch.put(prefix + 't/s/a/' + id + '/' + pad32(tx.ps) + '/' + hash, DUMMY); + batch.put(prefix + 't/p/a/' + id + '/' + hash, DUMMY); }); utils.forEachSerial(tx.outputs, function(output, next, i) { - var address = output.getAddress(); - - if (address && !uniq[address]) { - uniq[address] = true; - map[address].forEach(function(id) { - batch.put(prefix + 'p/a/' + id + '/' + hash, DUMMY); - }); - } - self.getCoin(hash, i, function(err, coin) { if (err) return next(err); @@ -897,8 +872,8 @@ TXPool.prototype.getPendingHashes = function getPendingHashes(address, callback) } iter = this.db.db.iterator({ - gte: address ? prefix + 'p/a/' + address : prefix + 'p/t', - lte: address ? prefix + 'p/a/' + address + '~' : prefix + 'p/t~', + gte: address ? prefix + 't/p/a/' + address : prefix + 't/p/t', + lte: address ? prefix + 't/p/a/' + address + '~' : prefix + 't/p/t~', keys: true, values: false, fillCache: false, @@ -994,41 +969,27 @@ TXPool.prototype.getCoinIDs = function getCoinIDs(address, callback) { })(); }; -TXPool.prototype.getHeightHashes = function getHeightHashes(height, callback) { +TXPool.prototype.getHeightRangeHashes = function getHeightRangeHashes(address, options, callback) { var prefix = this.prefix + '/'; var self = this; var txs = []; + var total = 0; var iter; callback = utils.ensure(callback); - if (Array.isArray(height)) { - return utils.forEachSerial(height, function(height, next) { - self.getHeightHashes(height, function(err, tx) { - if (err) - return next(err); - - txs = txs.concat(tx); - - next(); - }); - }, function(err) { - if (err) - return callback(err); - - txs = utils.uniqs(txs); - - return callback(null, txs); - }); - } - iter = this.db.db.iterator({ - gte: prefix + 't/h/' + height, - lte: prefix + 't/h/' + height + '~', + gte: address + ? prefix + 't/h/a/' + address + '/' + pad32(options.start) + '/' + : prefix + 't/h/h/' + pad32(options.start) + '/', + lte: address + ? prefix + 't/h/a/' + address + '/' + pad32(options.end) + '/~' + : prefix + 't/h/h/' + pad32(options.end) + '/~', keys: true, values: false, fillCache: false, - keyAsBuffer: false + keyAsBuffer: false, + reverse: options.reverse }); (function next() { @@ -1049,11 +1010,105 @@ TXPool.prototype.getHeightHashes = function getHeightHashes(height, callback) { txs.push(key.split('/')[4]); + if (++total === options.limit) + return callback(null, txs); + next(); }); })(); }; +TXPool.prototype.getHeightHashes = function getHeightHashes(height, callback) { + return this.getHeightRangeHashes({ start: height, end: height }, callback); +}; + +TXPool.prototype.getTimeRangeHashes = function getTimeRangeHashes(address, options, callback) { + var prefix = this.prefix + '/'; + var self = this; + var txs = []; + var total = 0; + var iter; + + callback = utils.ensure(callback); + + iter = this.db.db.iterator({ + gte: address + ? prefix + 't/s/a/' + address + '/' + pad32(options.start) + '/' + : prefix + 't/s/s/' + pad32(options.start) + '/', + lte: address + ? prefix + 't/s/a/' + address + '/' + pad32(options.end) + '/~' + : prefix + 't/s/s/' + pad32(options.end) + '/~', + keys: true, + values: false, + fillCache: false, + keyAsBuffer: false, + reverse: options.reverse + }); + + (function next() { + iter.next(function(err, key, value) { + if (err) { + return iter.end(function() { + callback(err); + }); + } + + if (key === undefined) { + return iter.end(function(err) { + if (err) + return callback(err); + return callback(null, txs); + }); + } + + txs.push(key.split('/')[4]); + + if (++total === options.limit) + return callback(null, txs); + + next(); + }); + })(); +}; + +TXPool.prototype.getTimeRange = function getLast(address, options, callback) { + var self = this; + var txs = []; + + return this.getTimeRangeHashes(address, options, function(err, hashes) { + if (err) + return callback(err); + + utils.forEachSerial(hashes, function(hash, next) { + self.getTX(hash, function(err, tx) { + if (err) + return callback(err); + + if (!tx) + return next(); + + txs.push(tx); + + next(); + }); + }, function(err) { + if (err) + return callback(err); + + return callback(null, txs); + }); + }); +}; + +TXPool.prototype.getLast = function getLast(address, limit, callback) { + return this.getTimeRange(address, { + start: 0, + end: 0xffffffff, + reverse: true, + limit: limit + }, callback); +}; + TXPool.prototype.getAllByAddress = function getAllByAddress(address, callback) { var self = this; var txs = []; @@ -1083,7 +1138,7 @@ TXPool.prototype.getAllByAddress = function getAllByAddress(address, callback) { }); }; -TXPool.prototype.getLast = function getLast(address, callback) { +TXPool.prototype.getLastTime = function getLastTime(address, callback) { return this.getAllByAddress(address, function(err, txs) { var lastTs, lastHeight; diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index bd09886a..8e8748f6 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -152,12 +152,14 @@ Wallet.prototype._init = function _init() { }); this.provider.on('unconfirmed', function(tx) { - self.syncOutputDepth(tx); self.emit('unconfirmed', tx); }); }; Wallet.prototype.destroy = function destroy() { + if (!this.provider) + return; + this.provider.destroy(); this.provider = null; }; @@ -216,9 +218,6 @@ Wallet.prototype.removeKey = function removeKey(key) { else if (bcoin.hd.publicKey.isExtended(key)) key = bcoin.hd.publicKey(key); - if (key instanceof bcoin.keypair) - key = key.hd; - if (key instanceof bcoin.hd.privateKey) key = key.hdPublicKey; @@ -428,7 +427,7 @@ Wallet.prototype.parsePath = function parsePath(path) { Wallet.prototype.setReceiveDepth = function setReceiveDepth(depth) { var i; - if (depth <= this.receiveDepth) + if (!(depth > this.receiveDepth)) return false; for (i = this.receiveDepth; i < depth; i++) @@ -445,7 +444,7 @@ Wallet.prototype.setReceiveDepth = function setReceiveDepth(depth) { Wallet.prototype.setChangeDepth = function setChangeDepth(depth) { var i; - if (depth <= this.changeDepth) + if (!(depth > this.changeDepth)) return false; for (i = this.changeDepth; i < depth; i++) @@ -674,33 +673,30 @@ Wallet.prototype.syncOutputDepth = function syncOutputDepth(tx) { var depth = this.getOutputDepth(tx); var res = false; - if (depth.changeDepth >= this.changeDepth) { - this.setChangeDepth(depth.changeDepth + 1); + if (this.setChangeDepth(depth.changeDepth + 1)) res = true; - } - if (depth.receiveDepth >= this.receiveDepth) { - this.setReceiveDepth(depth.receiveDepth + 1); + if (this.setReceiveDepth(depth.receiveDepth + 1)) res = true; - } return res; }; Wallet.prototype.scan = function scan(txByAddress, callback) { var self = this; + var res = false; return this._scan({}, txByAddress, function(err, depth) { if (err) return callback(err); - if (depth.changeDepth >= self.changeDepth) - self.setChangeDepth(depth.changeDepth + 1); + if (self.setChangeDepth(depth.changeDepth + 1)) + res = true; - if (depth.receiveDepth >= self.receiveDepth) - self.setReceiveDepth(depth.receiveDepth + 1); + if (self.setReceiveDepth(depth.receiveDepth + 1)) + res = true; - return callback(null, self); + return callback(null, res); }); }; @@ -822,11 +818,25 @@ Wallet.prototype.getBalance = function getBalance(callback) { return this.provider.getBalance(callback); }; -Wallet.prototype.getLast = function getLast(callback) { +Wallet.prototype.getLastTime = function getLastTime(callback) { if (!this.provider) return callback(new Error('No wallet provider available.')); - return this.provider.getLast(callback); + return this.provider.getLastTime(callback); +}; + +Wallet.prototype.getLast = function getLast(limit, callback) { + if (!this.provider) + return callback(new Error('No wallet provider available.')); + + return this.provider.getLast(limit, callback); +}; + +Wallet.prototype.getTimeRange = function getTimeRange(options, callback) { + if (!this.provider) + return callback(new Error('No wallet provider available.')); + + return this.provider.getTimeRange(options, callback); }; Wallet.prototype.getPrivateKey = function getPrivateKey(enc) { @@ -957,9 +967,6 @@ Wallet.prototype.toJSON = function toJSON() { receiveDepth: this.receiveDepth, changeDepth: this.changeDepth, master: this.master.toJSON(this.options.passphrase), - // Store account key to: - // a. save ourselves derivations. - // b. allow deriving of addresses without decrypting the master key. accountKey: this.accountKey.xpubkey, addressMap: this.addressMap, keys: this.keys.map(function(key) { @@ -993,10 +1000,15 @@ Wallet._fromJSON = function _fromJSON(json, passphrase) { }; }; +Wallet.fromJSON = function fromJSON(json, passphrase) { + return new Wallet(Wallet._fromJSON(json, passphrase)); +}; + // For updating the address table quickly // without decrypting the master key. -Wallet.sync = function sync(json, options) { +Wallet._syncDepth = function sync(json, options) { var master, wallet; + var res = false; assert.equal(json.v, 3); assert.equal(json.name, 'wallet'); @@ -1008,17 +1020,26 @@ Wallet.sync = function sync(json, options) { json.master = json.accountKey; wallet = new Wallet(json); - if (options.txs != null) { - options.txs.forEach(function(tx) { - wallet.syncOutputDepth(tx); - }); + if (!wallet._initialized) + return; + + if (options.tx != null) { + if (wallet.syncOutputDepth(options.tx)) + res = true; } - if (options.receiveDepth != null) - wallet.setReceiveDepth(options.receiveDepth); + if (options.receiveDepth != null) { + if (wallet.setReceiveDepth(options.receiveDepth)) + res = true; + } - if (options.changeDepth != null) - wallet.setChangeDepth(options.changeDepth); + if (options.changeDepth != null) { + if (wallet.setChangeDepth(options.changeDepth)) + res = true; + } + + if (!res) + return; wallet = wallet.toJSON(); wallet.master = master; @@ -1026,8 +1047,27 @@ Wallet.sync = function sync(json, options) { return wallet; }; -Wallet.fromJSON = function fromJSON(json, passphrase) { - return new Wallet(Wallet._fromJSON(json, passphrase)); +Wallet.syncOutputDepth = function syncOutputDepth(json, tx) { + return Wallet._syncDepth(json, { tx: tx }); +}; + +Wallet.setReceiveDepth = function setReceiveDepth(json, receiveDepth) { + return Wallet._syncDepth(json, { + receiveDepth: receiveDepth || 0 + }); +}; + +Wallet.setChangeDepth = function setChangeDepth(json, changeDepth) { + return Wallet._syncDepth(json, { + changeDepth: changeDepth || 0 + }); +}; + +Wallet.setDepth = function setDepth(json, receiveDepth, changeDepth) { + return Wallet._syncDepth(json, { + receiveDepth: receiveDepth || 0, + changeDepth: changeDepth || 0 + }); }; /** diff --git a/lib/bcoin/walletdb.js b/lib/bcoin/walletdb.js index faef2fbf..76d4efc8 100644 --- a/lib/bcoin/walletdb.js +++ b/lib/bcoin/walletdb.js @@ -166,22 +166,7 @@ WalletDB.prototype._init = function _init() { return; utils.forEachSerial(map.output, function(id, next) { - self.getJSON(id, function(err, json) { - if (err) { - self.emit('error', err); - return next(); - } - - // Allocate new addresses if necessary. - json = bcoin.wallet.sync(json, { txs: [tx] }); - - self.saveJSON(id, json, function(err) { - if (err) - return next(err); - - next(); - }); - }); + self.syncOutputDepth(id, tx, next); }, function(err) { if (err) self.emit('error', err); @@ -189,29 +174,53 @@ WalletDB.prototype._init = function _init() { }); }; -WalletDB.prototype.syncDepth = function syncDepth(id, changeDepth, receiveDepth, callback) { +WalletDB.prototype.syncOutputDepth = function syncOutputDepth(id, tx, callback) { + var self = this; + callback = utils.ensure(callback); - if (!receiveDepth) - receiveDepth = 0; - - if (!changeDepth) - changeDepth = 0; - - self.getJSON(id, function(err, json) { + this.getJSON(id, function(err, json) { if (err) return callback(err); // Allocate new addresses if necessary. - json = bcoin.wallet.sync(json, { - receiveDepth: receiveDepth, - changeDepth: changeDepth - }); + json = bcoin.wallet.syncOutputDepth(json, tx); + + if (!json) + return callback(); self.saveJSON(id, json, function(err) { if (err) return callback(err); - self.emit('sync depth', id, receiveDepth, changeDepth); + + self.emit('sync output depth', id, tx); + + callback(); + }); + }); +}; + +WalletDB.prototype.setDepth = function setDepth(id, receive, change, callback) { + var self = this; + + callback = utils.ensure(callback); + + this.getJSON(id, function(err, json) { + if (err) + return callback(err); + + // Allocate new addresses if necessary. + json = bcoin.wallet.setDepth(json, receive, change); + + if (!json) + return callback(); + + self.saveJSON(id, json, function(err) { + if (err) + return callback(err); + + self.emit('set depth', id, receive, change); + callback(); }); }); @@ -477,6 +486,7 @@ WalletDB.prototype.update = function update(wallet, address) { if (err) self.emit('error', err); + // XXX might have to encrypt key - slow self._saveDB(wallet.id, wallet.toJSON(), function(err) { if (err) self.emit('error', err); @@ -520,10 +530,22 @@ WalletDB.prototype.getBalance = function getBalance(id, callback) { return this.tx.getBalanceByAddress(id, callback); }; -WalletDB.prototype.getLast = function getLast(id, callback) { +WalletDB.prototype.getLastTime = function getLastTime(id, callback) { var self = this; id = id.id || id; - return this.tx.getLast(id, callback); + return this.tx.getLastTime(id, callback); +}; + +WalletDB.prototype.getLast = function getLast(id, limit, callback) { + var self = this; + id = id.id || id; + return this.tx.getLast(id, limit, callback); +}; + +WalletDB.prototype.getTimeRange = function getTimeRange(id, options, callback) { + var self = this; + id = id.id || id; + return this.tx.getTimeRange(id, options, callback); }; WalletDB.prototype.fillTX = function fillTX(tx, callback) { @@ -650,8 +672,16 @@ Provider.prototype.getBalance = function getBalance(callback) { return this.db.getBalance(this.id, callback); }; -Provider.prototype.getLast = function getLast(callback) { - return this.db.getLast(this.id, callback); +Provider.prototype.getLastTime = function getLastTime(callback) { + return this.db.getLastTime(this.id, callback); +}; + +Provider.prototype.getLast = function getLast(limit, callback) { + return this.db.getLast(this.id, limit, callback); +}; + +Provider.prototype.getTimeRange = function getTimeRange(options, callback) { + return this.db.getTimeRange(this.id, options, callback); }; Provider.prototype.getTX = function getTX(hash, callback) {