From ad8090dc7cafb698df15d759e52174f935e94899 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 2 Mar 2016 15:20:39 -0800 Subject: [PATCH] tx-by-height. update node and pool to use walletdb. --- lib/bcoin/node.js | 26 ++-- lib/bcoin/node2.js | 1 - lib/bcoin/pool.js | 21 +-- lib/bcoin/txdb.js | 369 ++++++++++++++++++++++++++------------------ lib/bcoin/wallet.js | 15 +- 5 files changed, 238 insertions(+), 194 deletions(-) diff --git a/lib/bcoin/node.js b/lib/bcoin/node.js index 2f0077f4..73d1d967 100644 --- a/lib/bcoin/node.js +++ b/lib/bcoin/node.js @@ -100,13 +100,9 @@ Fullnode.prototype._init = function _init() { // Emit events for any TX we see that's // is relevant to one of our wallets. this.on('tx', function(tx) { - return; - self.walletdb.ownTX(tx, function(err, input, output) { - if (err) - return self.emit('error', err); - - if (input || output) - self.emit('wallet tx', tx, input || [], output || []); + self.walletdb.tx.addTX(tx, function(err, updated) { + if (updated) + self.emit('wallet tx', tx); }); }); @@ -134,10 +130,12 @@ Fullnode.prototype._init = function _init() { // Handle forks by unconfirming txs // in our wallets' tx pools. this.chain.on('remove entry', function(entry) { - self.wallets.forEach(function(wallet) { - wallet.tx.getAll().forEach(function(tx) { - if (tx.block === entry.hash || tx.height >= entry.height) - wallet.tx.unconfirm(tx); + self.walletdb.tx.getHeightHashes(entry.height, function(err, txs) { + if (err) + return self.emit('error', err); + + txs.forEach(function(tx) { + self.walletdb.tx.unconfirm(tx); }); }); }); @@ -186,11 +184,7 @@ Fullnode.prototype.createWallet = function createWallet(options, callback) { assert(wallet); utils.debug('Loaded wallet with id=%s address=%s', - wallet.getID(), wallet.getAddress()); - - self.wallets.push(wallet); - - return callback(null, wallet); + wallet.id, wallet.getAddress()); self.pool.addWallet(wallet, function(err) { if (err) diff --git a/lib/bcoin/node2.js b/lib/bcoin/node2.js index 477d9433..8ef701ab 100644 --- a/lib/bcoin/node2.js +++ b/lib/bcoin/node2.js @@ -44,7 +44,6 @@ function Node(options) { this.chain = null; this.miner = null; this.profiler = null; - this.wallets = []; Node.global = this; } diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index f4d08fec..b80c6d8f 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -139,10 +139,6 @@ function Pool(node, options) { timeout: options.invTimeout || 60000 }; - // Added and watched wallets - options.wallets = options.wallets || []; - this.wallets = []; - Pool.global = this; this.loading = true; @@ -230,7 +226,7 @@ Pool.prototype._init = function _init() { self.resolveOrphan(self.peers.load, null, data.hash); }); - this.options.wallets.forEach(function(wallet) { + (this.options.wallets || []).forEach(function(wallet) { self.addWallet(wallet); }); @@ -1226,13 +1222,8 @@ Pool.prototype.addWallet = function addWallet(wallet, callback) { callback = utils.asyncify(callback); - if (this.options.spv) { - if (this.wallets.indexOf(wallet) !== -1) - return callback(); - + if (this.options.spv) this.watchWallet(wallet); - this.wallets.push(wallet); - } wallet.getPending(function(err, txs) { if (err) @@ -1250,19 +1241,11 @@ Pool.prototype.addWallet = function addWallet(wallet, callback) { }; Pool.prototype.removeWallet = function removeWallet(wallet) { - var i; - if (!this.options.spv) return; - i = this.wallets.indexOf(wallet); - assert(!this.loading); - if (i == -1) - return; - - this.wallets.splice(i, 1); this.unwatchWallet(wallet); }; diff --git a/lib/bcoin/txdb.js b/lib/bcoin/txdb.js index 8f75fa4c..c220ac0b 100644 --- a/lib/bcoin/txdb.js +++ b/lib/bcoin/txdb.js @@ -116,7 +116,8 @@ TXPool.prototype._add = function add(tx, callback, force) { // and remove pending flag to mark as confirmed. if (existing.ts === 0 && tx.ts !== 0) { batch.put(p + 't/t/' + hash, tx.toExtended()); - batch.del(p + 'p/' + hash); + batch.put(p + 't/h/' + tx.height + '/' + hash, new Buffer([])); + batch.del(p + 'p/t/' + hash); tx.inputs.forEach(function(input) { var type = input.getType(); @@ -401,7 +402,9 @@ TXPool.prototype._add = function add(tx, callback, force) { batch.put(p + 't/t/' + hash, tx.toExtended()); if (tx.ts === 0) - batch.put(p + 'p/' + hash, new Buffer([])); + batch.put(p + 'p/t/' + hash, new Buffer([])); + else + batch.put(p + 't/h/' + tx.height + '/' + hash, new Buffer([])); tx.getAddresses().forEach(function(address) { batch.put(p + 't/a/' + address + '/' + hash, new Buffer([])); @@ -451,7 +454,9 @@ TXPool.prototype.remove = function remove(hash, callback) { batch.del(p + 't/t/' + hash); if (tx.ts === 0) - batch.del(p + 'p/' + hash); + batch.del(p + 'p/t/' + hash); + else + batch.del(p + 't/h/' + tx.height + '/' + hash); self.fillTX(tx, function(err) { if (err) @@ -465,9 +470,6 @@ TXPool.prototype.remove = function remove(hash, callback) { if (input.isCoinbase()) return; - if (!input.output) - return; - if (type === 'pubkey' || type === 'multisig') address = null; @@ -552,7 +554,7 @@ TXPool.prototype.unconfirm = function unconfirm(hash, callback) { hash = hash.hash('hex'); this.getTX(hash, function(err, tx) { - var batch; + var batch, height; if (err) return callback(err); @@ -560,6 +562,7 @@ TXPool.prototype.unconfirm = function unconfirm(hash, callback) { if (!tx) return callback(null, true); + height = tx.height; tx.height = -1; tx.ps = utils.now(); tx.ts = 0; @@ -567,7 +570,8 @@ TXPool.prototype.unconfirm = function unconfirm(hash, callback) { tx.block = null; batch.put(p + 't/t/' + hash, tx.toExtended()); - batch.put(p + 'p/' + hash, new Buffer([])); + batch.put(p + 'p/t/' + hash, new Buffer([])); + batch.del(p + 't/h/' + height + '/' + hash); tx.inputs.forEach(function(input) { var type = input.getType(); @@ -644,6 +648,7 @@ TXPool.prototype.getTXHashes = function getTXHashes(address, callback) { var p = this.prefix + '/'; var self = this; var txs = []; + var iter; callback = utils.ensure(callback); @@ -667,9 +672,197 @@ TXPool.prototype.getTXHashes = function getTXHashes(address, callback) { }); } - var iter = this.db.db.iterator({ - gte: p + 't/a/' + address, - lte: p + 't/a/' + address + '~', + iter = this.db.db.iterator({ + gte: address ? p + 't/a/' + address : p + 't/t', + lte: address ? p + 't/a/' + address + '~' : p + 't/t~', + keys: true, + values: false, + fillCache: false, + keyAsBuffer: false + }); + + (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); + }); + } + + if (address) + txs.push(key.split('/')[4]); + else + txs.push(key.split('/')[3]); + + next(); + }); + })(); +}; + +TXPool.prototype.getPendingHashes = function getPendingHashes(address, callback) { + var p = this.prefix + '/'; + var self = this; + var txs = []; + var iter; + + callback = utils.ensure(callback); + + if (Array.isArray(address)) { + return utils.forEachSerial(address, function(address, next) { + assert(address); + self.getPendingHashes(address, 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: address ? p + 'p/a/' + address : p + 'p/t', + lte: address ? p + 'p/a/' + address + '~' : p + 'p/t~', + keys: true, + values: false, + fillCache: false, + keyAsBuffer: false + }); + + (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); + }); + } + + if (address) + txs.push(key.split('/')[4]); + else + txs.push(key.split('/')[3]); + + next(); + }); + })(); +}; + +TXPool.prototype.getCoinIDs = function getCoinIDs(address, callback) { + var p = this.prefix + '/'; + var self = this; + var coins = []; + var iter; + + callback = utils.ensure(callback); + + if (Array.isArray(address)) { + return utils.forEachSerial(address, function(address, next) { + self.getCoinIDs(address, function(err, coin) { + if (err) + return next(err); + + coins = coins.concat(coin); + + next(); + }); + }, function(err) { + if (err) + return callback(err); + + coins = utils.uniqs(coins); + + return callback(null, coins); + }); + } + + iter = this.db.db.iterator({ + gte: address ? p + 'u/a/' + address : p + 'u/t', + lte: address ? p + 'u/a/' + address + '~' : p + 'u/t~', + keys: true, + values: false, + fillCache: false, + keyAsBuffer: false + }); + + (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, coins); + }); + } + + if (address) + coins.push(key.split('/').slice(4).join('/')); + else + coins.push(key.split('/').slice(3).join('/')); + + next(); + }); + })(); +}; + +TXPool.prototype.getHeightHashes = function getHeightHashes(height, callback) { + var p = this.prefix + '/'; + var self = this; + var txs = []; + 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); + + return callback(null, txs); + }); + } + + iter = this.db.db.iterator({ + gte: p + 't/h/' + height, + lte: p + 't/h/' + height + '~', keys: true, values: false, fillCache: false, @@ -699,128 +892,6 @@ TXPool.prototype.getTXHashes = function getTXHashes(address, callback) { })(); }; -TXPool.prototype.getPendingHashes = function getPendingHashes(address, callback) { - var p = this.prefix + '/'; - var self = this; - var txs = []; - - callback = utils.ensure(callback); - - if (Array.isArray(address)) { - return utils.forEachSerial(address, function(address, next) { - assert(address); - self.getPendingHashes(address, 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); - }); - } - - var iter = this.db.db.iterator({ - gte: address ? p + 'p/a/' + address : p + 'p', - lte: address ? p + 'p/a/' + address + '~' : p + 'p~', - keys: true, - values: false, - fillCache: false, - keyAsBuffer: false - }); - - (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); - }); - } - - if (address) - txs.push(key.split('/')[4]); - else - txs.push(key.split('/')[2]); - - next(); - }); - })(); -}; - -TXPool.prototype.getCoinIDs = function getCoinIDs(address, callback) { - var p = this.prefix + '/'; - var self = this; - var coins = []; - - callback = utils.ensure(callback); - - if (Array.isArray(address)) { - return utils.forEachSerial(address, function(address, next) { - self.getCoinIDs(address, function(err, coin) { - if (err) - return next(err); - - coins = coins.concat(coin); - - next(); - }); - }, function(err) { - if (err) - return callback(err); - - coins = utils.uniqs(coins); - - return callback(null, coins); - }); - } - - var iter = this.db.db.iterator({ - gte: p + 'u/a/' + address, - lte: p + 'u/a/' + address + '~', - keys: true, - values: false, - fillCache: false, - keyAsBuffer: false - }); - - (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, coins); - }); - } - - coins.push(key.split('/').slice(4).join('/')); - - next(); - }); - })(); -}; - TXPool.prototype.getTXByAddress = function getTXByAddress(address, callback) { var self = this; var txs = []; @@ -829,9 +900,6 @@ TXPool.prototype.getTXByAddress = function getTXByAddress(address, callback) { if (err) return callback(err); - if (!hashes.length) - return callback(null, hashes); - utils.forEachSerial(hashes, function(hash, next) { self.getTX(hash, function(err, tx) { if (err) @@ -860,7 +928,7 @@ TXPool.prototype.getLast = function getLast(address, callback) { if (err) return callback(err); - lastTs = -1; + lastTs = 0; lastHeight = -1; txs.forEach(function(tx) { @@ -871,9 +939,6 @@ TXPool.prototype.getLast = function getLast(address, callback) { lastHeight = tx.height; }); - if (lastTs === -1) - lastTs = utils.now() - 2 * 7 * 24 * 60 * 60; - return callback(null, lastTs, lastHeight); }); }; @@ -885,9 +950,6 @@ TXPool.prototype.getPendingByAddress = function getPendingByAddress(address, cal if (err) return callback(err); - if (!hashes.length) - return callback(null, hashes); - utils.forEachSerial(hashes, function(hash, next) { self.getTX(hash, function(err, tx) { if (err) @@ -917,9 +979,6 @@ TXPool.prototype.getCoinByAddress = function getCoinByAddress(address, callback) if (err) return callback(err); - if (!ids.length) - return callback(null, ids); - utils.forEachSerial(ids, function(id, next) { var parts = id.split('/'); self.getCoin(parts[0], +parts[1], function(err, coin) { @@ -1049,6 +1108,22 @@ TXPool.prototype.getCoin = function getCoin(hash, index, callback) { }); }; +TXPool.prototype.getAll = function getAll(callback) { + return this.getTXByAddress(null, callback); +}; + +TXPool.prototype.getUnspent = function getUnspent(callback) { + return this.getCoinByAddress(null, callback); +}; + +TXPool.prototype.getPending = function getPending(callback) { + return this.getPendingByAddress(null, callback); +}; + +TXPool.prototype.getBalance = function getBalance(callback) { + return this.getBalanceByAddress(null, callback); +}; + TXPool.prototype.getAllByAddress = function getAllByAddress(address, callback) { return this.getTXByAddress(address, callback); }; @@ -1057,7 +1132,7 @@ TXPool.prototype.getUnspentByAddress = function getUnspentByAddress(address, cal return this.getCoinByAddress(address, callback); }; -TXPool.prototype.getPendingByAddress = function getPendingByAddress(address) { +TXPool.prototype.getPendingByAddress = function getPendingByAddress(address, callback) { return this.getPendingByAddress(address, callback); }; diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index e347a98d..21776384 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -531,9 +531,11 @@ Wallet.prototype.tx = function _tx(options, outputs, callback) { return callback(new Error('Incompatible locktime.')); // Set the locktime to target value or - // `height - whatever` to avoid fee snipping. + // `height - whatever` to avoid fee sniping. if (target.value > 0) tx.setLocktime(target.value); + else if (options.locktime != null) + tx.setLocktime(options.locktime); else tx.avoidFeeSniping(); @@ -541,16 +543,7 @@ Wallet.prototype.tx = function _tx(options, outputs, callback) { if (!self.sign(tx)) return callback(new Error('Could not sign transaction.')); - if (self.m > 1) - return callback(null, tx); - - // Add to pool - self.addTX(tx, function(err) { - if (err) - return callback(err); - - callback(null, tx); - }); + return callback(null, tx); }); };