From 5bb21070c42a6300c542407af5dd62ead019ac40 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 5 Oct 2016 19:12:21 -0700 Subject: [PATCH] wallet: refactor and add global zap. --- bin/cli | 9 ++++ lib/http/client.js | 11 ++++ lib/http/server.js | 12 +++++ lib/node/fullnode.js | 3 ++ lib/node/spvnode.js | 3 ++ lib/wallet/txdb.js | 8 +++ lib/wallet/walletdb.js | 112 ++++++++++++++++++++++++++++++++++------- 7 files changed, 140 insertions(+), 18 deletions(-) diff --git a/bin/cli b/bin/cli index 7557c5ef..d7c9b2ee 100755 --- a/bin/cli +++ b/bin/cli @@ -303,6 +303,12 @@ CLI.prototype.rescan = co(function* rescan() { this.log('Rescanning...'); }); +CLI.prototype.zapAll = co(function* zapAll() { + var age = +this.argv[0] || 72 * 3600; + yield this.client.zapAll(age); + this.log('Zapped.'); +}); + CLI.prototype.backup = co(function* backup() { var path = this.argv[0]; @@ -493,6 +499,8 @@ CLI.prototype.handleNode = co(function* handleNode() { return yield this.getBlock(); case 'rescan': return yield this.rescan(); + case 'zap': + return yield this.zapAll(); case 'backup': return yield this.backup(); case 'rpc': @@ -507,6 +515,7 @@ CLI.prototype.handleNode = co(function* handleNode() { this.log(' $ coin [hash+index/address]: View coins.'); this.log(' $ block [hash/height]: View block.'); this.log(' $ rescan [height/hash]: Rescan for transactions.'); + this.log(' $ zap [age?]: Zap stale transactions from walletdb.'); this.log(' $ backup [path]: Backup the wallet db.'); this.log(' $ rpc [command] [args]: Execute RPC command.'); return; diff --git a/lib/http/client.js b/lib/http/client.js index 02c195e1..bc28102b 100644 --- a/lib/http/client.js +++ b/lib/http/client.js @@ -358,6 +358,17 @@ HTTPClient.prototype.rescan = function rescan(hash) { return this._post('/rescan', options); }; +/** + * Zap stale transactions. + * @param {Number} age + * @returns {Promise} + */ + +HTTPClient.prototype.zapAll = function zapAll(age) { + var options = { age: age }; + return this._post('/zap', options); +}; + /** * Backup the walletdb. * @param {String} path diff --git a/lib/http/server.js b/lib/http/server.js index f9735765..df5957e1 100644 --- a/lib/http/server.js +++ b/lib/http/server.js @@ -698,6 +698,18 @@ HTTPServer.prototype._init = function _init() { yield this.node.scan(height); })); + // Zap + this.post('/zap', con(function* (req, res, send, next) { + var options = req.options; + var age = options.age; + + enforce(age != null, 'Age is required.'); + + yield this.walletdb.zap(age); + + send(200, { success: true }); + })); + // Backup WalletDB this.post('/backup', con(function* (req, res, send, next) { var options = req.options; diff --git a/lib/node/fullnode.js b/lib/node/fullnode.js index 74501ce6..00c73dc7 100644 --- a/lib/node/fullnode.js +++ b/lib/node/fullnode.js @@ -245,6 +245,9 @@ Fullnode.prototype._open = co(function* open() { // Ensure primary wallet. yield this.openWallet(); + // Zap stale txs. + yield this.walletdb.zap(72 * 3600); + // Rescan for any missed transactions. yield this.rescan(); diff --git a/lib/node/spvnode.js b/lib/node/spvnode.js index 6218dcc8..afaf05bf 100644 --- a/lib/node/spvnode.js +++ b/lib/node/spvnode.js @@ -168,6 +168,9 @@ SPVNode.prototype._open = co(function* open(callback) { // Load bloom filter. yield this.openFilter(); + // Zap stale txs. + yield this.walletdb.zap(72 * 3600); + // Rescan for any missed transactions. yield this.rescan(); diff --git a/lib/wallet/txdb.js b/lib/wallet/txdb.js index f8cdf4e0..441d34cd 100644 --- a/lib/wallet/txdb.js +++ b/lib/wallet/txdb.js @@ -1918,6 +1918,7 @@ TXDB.prototype.getAccountBalance = co(function* getBalance(account) { */ TXDB.prototype.zap = co(function* zap(account, age) { + var hashes = []; var i, txs, tx, hash; if (!utils.isUInt32(age)) @@ -1937,6 +1938,9 @@ TXDB.prototype.zap = co(function* zap(account, age) { this.wallet.start(); + this.logger.debug('Zapping TX: %s (%s)', + hash, this.wallet.id); + try { yield this._remove(hash); } catch (e) { @@ -1944,8 +1948,12 @@ TXDB.prototype.zap = co(function* zap(account, age) { throw e; } + hashes.push(hash); + yield this.wallet.commit(); } + + return hashes; }); /** diff --git a/lib/wallet/walletdb.js b/lib/wallet/walletdb.js index 8aa2fcd5..3f812ee1 100644 --- a/lib/wallet/walletdb.js +++ b/lib/wallet/walletdb.js @@ -25,6 +25,7 @@ var ldb = require('../db/ldb'); var Bloom = require('../utils/bloom'); var Logger = require('../node/logger'); var TX = require('../primitives/tx'); +var TXDB = require('./txdb'); /* * Database Layout: @@ -743,7 +744,7 @@ WalletDB.prototype.saveAccount = function saveAccount(account) { var wallet = account.wallet; var index = account.accountIndex; var name = account.name; - var batch = this.batch(account.wallet); + var batch = this.batch(wallet); var buf = new Buffer(4); buf.writeUInt32LE(index, 0, true); @@ -1043,12 +1044,10 @@ WalletDB.prototype._rescan = co(function* rescan(chaindb, height) { */ WalletDB.prototype.getPendingKeys = co(function* getPendingKeys() { - var layout = require('./txdb').layout; + var layout = TXDB.layout; var dummy = new Buffer(0); - var uniq = {}; var keys = []; - var result = []; - var i, iter, item, key, wid, hash; + var iter, item; iter = yield this.db.iterator({ gte: layout.prefix(0x00000000, dummy), @@ -1065,6 +1064,22 @@ WalletDB.prototype.getPendingKeys = co(function* getPendingKeys() { keys.push(item.key); } + return keys; +}); + +/** + * Get keys of all pending transactions + * in the wallet db (for resending). + * @returns {Promise} + */ + +WalletDB.prototype.getPendingTX = co(function* getPendingTX() { + var layout = TXDB.layout; + var keys = yield this.getPendingKeys(); + var uniq = {}; + var result = []; + var i, key, wid, hash; + for (i = 0; i < keys.length; i++) { key = keys[i]; @@ -1083,13 +1098,41 @@ WalletDB.prototype.getPendingKeys = co(function* getPendingKeys() { return result; }); +/** + * Get all wallet IDs with pending txs in them. + * @returns {Promise} + */ + +WalletDB.prototype.getPendingWallets = co(function* getPendingWallets() { + var layout = TXDB.layout; + var keys = yield this.getPendingKeys(); + var uniq = {}; + var result = []; + var i, key, wid; + + for (i = 0; i < keys.length; i++) { + key = keys[i]; + + wid = layout.pre(key); + + if (uniq[wid]) + continue; + + uniq[wid] = true; + + result.push(wid); + } + + return result; +}); + /** * Resend all pending transactions. * @returns {Promise} */ WalletDB.prototype.resend = co(function* resend() { - var keys = yield this.getPendingKeys(); + var keys = yield this.getPendingTX(); var i, key, data, tx; if (keys.length > 0) @@ -1114,7 +1157,7 @@ WalletDB.prototype.resend = co(function* resend() { * @returns {Promise} */ -WalletDB.prototype.getWidsByHashes = co(function* getWidsByHashes(hashes) { +WalletDB.prototype.getWalletsByHashes = co(function* getWalletsByHashes(hashes) { var result = []; var i, j, hash, wids; @@ -1126,10 +1169,16 @@ WalletDB.prototype.getWidsByHashes = co(function* getWidsByHashes(hashes) { wids = yield this.getWalletsByHash(hash); + if (!wids) + continue; + for (j = 0; j < wids.length; j++) utils.binaryInsert(result, wids[j], cmp, true); } + if (result.length === 0) + return; + return result; }); @@ -1398,13 +1447,10 @@ WalletDB.prototype._addTX = co(function* addTX(tx) { assert(!tx.mutable, 'Cannot add mutable TX to wallet.'); - // Atomicity doesn't matter here. If we crash, - // the automatic rescan will get the database - // back in the correct state. hashes = tx.getHashes('hex'); - wallets = yield this.getWidsByHashes(hashes); + wallets = yield this.getWalletsByHashes(hashes); - if (wallets.length === 0) + if (!wallets) return; this.logger.info( @@ -1415,8 +1461,7 @@ WalletDB.prototype._addTX = co(function* addTX(tx) { wid = wallets[i]; wallet = yield this.get(wid); - if (!wallet) - continue; + assert(wallet); this.logger.debug('Adding tx to wallet: %s', wallet.id); @@ -1459,14 +1504,45 @@ WalletDB.prototype._unconfirmTX = co(function* unconfirmTX(hash) { for (i = 0; i < wallets.length; i++) { wid = wallets[i]; wallet = yield this.get(wid); - - if (!wallet) - continue; - + assert(wallet); yield wallet.unconfirm(hash); } }); +/** + * Zap stale transactions. + * @param {Number} age + * @returns {Promise} + */ + +WalletDB.prototype.zap = co(function* zap(age) { + var unlock = yield this.txLock.lock(); + try { + return yield this._zap(age); + } finally { + unlock(); + } +}); + +/** + * Zap stale transactions without a lock. + * @private + * @param {Number} age + * @returns {Promise} + */ + +WalletDB.prototype._zap = co(function* zap(age) { + var wallets = yield this.getPendingWallets(); + var i, wid, wallet; + + for (i = 0; i < wallets.length; i++) { + wid = wallets[i]; + wallet = yield this.get(wid); + assert(wallet); + yield wallet.zap(age); + } +}); + /** * Wallet Block * @constructor