From 40bb08aed69e894b352bd316054ac80a38ca23c3 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 20 Oct 2017 04:25:36 -0700 Subject: [PATCH] wallet: better/less property tracking. --- lib/wallet/http.js | 54 ++++++++++++------------ lib/wallet/path.js | 10 +---- lib/wallet/txdb.js | 91 +++++++++++++++++++---------------------- lib/wallet/wallet.js | 5 +-- lib/wallet/walletdb.js | 15 +++---- lib/wallet/walletkey.js | 12 ------ test/wallet-test.js | 3 +- 7 files changed, 83 insertions(+), 107 deletions(-) diff --git a/lib/wallet/http.js b/lib/wallet/http.js index c62bb8bb..c26a68b9 100644 --- a/lib/wallet/http.js +++ b/lib/wallet/http.js @@ -44,10 +44,10 @@ function HTTPServer(options) { this.options = options; this.network = this.options.network; this.logger = this.options.logger.context('http'); - this.walletdb = this.options.walletdb; + this.wdb = this.options.walletdb; this.server = new HTTPBase(this.options); - this.rpc = this.walletdb.rpc; + this.rpc = this.wdb.rpc; this.init(); } @@ -124,7 +124,7 @@ HTTPServer.prototype.initRouter = function initRouter() { const token = valid.buf('token'); if (!this.options.walletAuth) { - const wallet = await this.walletdb.get(id); + const wallet = await this.wdb.get(id); if (!wallet) { res.send(404); @@ -138,7 +138,7 @@ HTTPServer.prototype.initRouter = function initRouter() { let wallet; try { - wallet = await this.walletdb.auth(id, token); + wallet = await this.wdb.auth(id, token); } catch (err) { this.logger.info('Auth failure for %s: %s.', id, err.message); res.error(403, err); @@ -162,12 +162,12 @@ HTTPServer.prototype.initRouter = function initRouter() { res.send(200, { success: true }); - await this.walletdb.rescan(height); + await this.wdb.rescan(height); }); // Resend this.post('/_admin/resend', async (req, res) => { - await this.walletdb.resend(); + await this.wdb.resend(); res.send(200, { success: true }); }); @@ -179,14 +179,14 @@ HTTPServer.prototype.initRouter = function initRouter() { enforce(path, 'Path is required.'); - await this.walletdb.backup(path); + await this.wdb.backup(path); res.send(200, { success: true }); }); // List wallets this.get('/_admin/wallets', async (req, res) => { - const wallets = await this.walletdb.getWallets(); + const wallets = await this.wdb.getWallets(); res.send(200, wallets); }); @@ -205,7 +205,7 @@ HTTPServer.prototype.initRouter = function initRouter() { this.post('/', async (req, res) => { const valid = req.valid(); - const wallet = await this.walletdb.create({ + const wallet = await this.wdb.create({ id: valid.str('id'), type: valid.str('type'), m: valid.u32('m'), @@ -227,7 +227,7 @@ HTTPServer.prototype.initRouter = function initRouter() { this.put('/:id', async (req, res) => { const valid = req.valid(); - const wallet = await this.walletdb.create({ + const wallet = await this.wdb.create({ id: valid.str('id'), type: valid.str('type'), m: valid.u32('m'), @@ -425,7 +425,7 @@ HTTPServer.prototype.initRouter = function initRouter() { const details = await req.wallet.getDetails(tx.hash('hex')); - res.send(200, details.toJSON(this.network)); + res.send(200, details.toJSON(this.network, this.wdb.height)); }); // Create TX @@ -724,7 +724,7 @@ HTTPServer.prototype.initRouter = function initRouter() { const result = []; for (const item of details) - result.push(item.toJSON(this.network)); + result.push(item.toJSON(this.network, this.wdb.height)); res.send(200, result); }); @@ -741,7 +741,7 @@ HTTPServer.prototype.initRouter = function initRouter() { const result = []; for (const item of details) - result.push(item.toJSON(this.network)); + result.push(item.toJSON(this.network, this.wdb.height)); res.send(200, result); }); @@ -763,7 +763,7 @@ HTTPServer.prototype.initRouter = function initRouter() { const result = []; for (const item of details) - result.push(item.toJSON(this.network)); + result.push(item.toJSON(this.network, this.wdb.height)); res.send(200, result); }); @@ -778,7 +778,7 @@ HTTPServer.prototype.initRouter = function initRouter() { const result = []; for (const item of details) - result.push(item.toJSON(this.network)); + result.push(item.toJSON(this.network, this.wdb.height)); res.send(200, result); }); @@ -799,7 +799,7 @@ HTTPServer.prototype.initRouter = function initRouter() { const details = await req.wallet.toDetails(tx); - res.send(200, details.toJSON(this.network)); + res.send(200, details.toJSON(this.network, this.wdb.height)); }); // Resend @@ -822,32 +822,32 @@ HTTPServer.prototype.initSockets = function initSockets() { this.handleSocket(socket); }); - this.walletdb.on('tx', (w, tx, details) => { - const json = details.toJSON(this.network); + this.wdb.on('tx', (w, tx, details) => { + const json = details.toJSON(this.network, this.wdb.height); this.to(`w:${w.id}`, 'wallet tx', json); }); - this.walletdb.on('confirmed', (w, tx, details) => { - const json = details.toJSON(this.network); + this.wdb.on('confirmed', (w, tx, details) => { + const json = details.toJSON(this.network, this.wdb.height); this.to(`w:${w.id}`, 'wallet confirmed', json); }); - this.walletdb.on('unconfirmed', (w, tx, details) => { - const json = details.toJSON(this.network); + this.wdb.on('unconfirmed', (w, tx, details) => { + const json = details.toJSON(this.network, this.wdb.height); this.to(`w:${w.id}`, 'wallet unconfirmed', json); }); - this.walletdb.on('conflict', (w, tx, details) => { - const json = details.toJSON(this.network); + this.wdb.on('conflict', (w, tx, details) => { + const json = details.toJSON(this.network, this.wdb.height); this.to(`w:${w.id}`, 'wallet conflict', json); }); - this.walletdb.on('balance', (w, balance) => { + this.wdb.on('balance', (w, balance) => { const json = balance.toJSON(); this.to(`w:${w.id}`, 'wallet balance', json); }); - this.walletdb.on('address', (w, receive) => { + this.wdb.on('address', (w, receive) => { const json = []; for (const addr of receive) @@ -917,7 +917,7 @@ HTTPServer.prototype.handleAuth = function handleAuth(socket) { let wallet; try { - wallet = await this.walletdb.auth(id, token); + wallet = await this.wdb.auth(id, token); } catch (e) { this.logger.info('Wallet auth failure for %s: %s.', id, e.message); throw new Error('Bad token.'); diff --git a/lib/wallet/path.js b/lib/wallet/path.js index ca2bc3a6..1acaea28 100644 --- a/lib/wallet/path.js +++ b/lib/wallet/path.js @@ -30,8 +30,6 @@ function Path(options) { this.keyType = Path.types.HD; - this.id = null; // Passed in by caller. - this.wid = -1; // Passed in by caller. this.name = null; // Passed in by caller. this.account = 0; this.branch = -1; @@ -71,8 +69,6 @@ Path.types = { Path.prototype.fromOptions = function fromOptions(options) { this.keyType = options.keyType; - this.id = options.id; - this.wid = options.wid; this.name = options.name; this.account = options.account; this.branch = options.branch; @@ -108,8 +104,6 @@ Path.prototype.clone = function clone() { path.keyType = this.keyType; - path.id = this.id; - path.wid = this.wid; path.name = this.name; path.account = this.account; path.branch = this.branch; @@ -247,8 +241,6 @@ Path.prototype.toRaw = function toRaw() { Path.prototype.fromAddress = function fromAddress(account, address) { this.keyType = Path.types.ADDRESS; - this.id = account.id; - this.wid = account.wid; this.name = account.name; this.account = account.accountIndex; this.version = address.version; @@ -309,7 +301,7 @@ Path.prototype.toJSON = function toJSON() { */ Path.prototype.inspect = function inspect() { - return ``; + return ``; }; /** diff --git a/lib/wallet/txdb.js b/lib/wallet/txdb.js index 1ed9cabf..34d9e2f3 100644 --- a/lib/wallet/txdb.js +++ b/lib/wallet/txdb.js @@ -25,22 +25,19 @@ const TXRecord = records.TXRecord; * TXDB * @alias module:wallet.TXDB * @constructor - * @param {Wallet} wallet + * @param {WalletDB} wdb */ -function TXDB(wdb) { +function TXDB(wdb, wid) { if (!(this instanceof TXDB)) return new TXDB(wdb); this.wdb = wdb; this.db = wdb.db; this.logger = wdb.logger; - this.network = wdb.network; - this.options = wdb.options; - this.wid = 0; - this.id = null; - this.prefix = layout.prefix(0); + this.wid = wid || 0; + this.prefix = layout.prefix(this.wid); this.wallet = null; this.locked = new Set(); } @@ -58,10 +55,8 @@ TXDB.layout = layout; */ TXDB.prototype.open = async function open(wallet) { - const {wid, id} = wallet; - this.id = id; - this.wid = wid; - this.prefix = layout.prefix(wid); + this.wid = wallet.wid; + this.prefix = layout.prefix(this.wid); this.wallet = wallet; }; @@ -147,7 +142,7 @@ TXDB.prototype.getPath = function getPath(output) { if (!hash) return null; - return this.wdb.getPath(this.wid, this.id, hash); + return this.wdb.getPath(this.wid, hash); }; /** @@ -392,18 +387,18 @@ TXDB.prototype.getBlock = async function getBlock(height) { /** * Append to the global block record. * @param {Hash} hash - * @param {BlockMeta} meta + * @param {BlockMeta} block * @returns {Promise} */ -TXDB.prototype.addBlock = async function addBlock(b, hash, meta) { - const key = layout.b(meta.height); +TXDB.prototype.addBlock = async function addBlock(b, hash, block) { + const key = layout.b(block.height); const data = await this.get(key); if (!data) { - const block = BlockRecord.fromMeta(meta); - block.add(hash); - b.put(key, block.toRaw()); + const blk = BlockRecord.fromMeta(block); + blk.add(hash); + b.put(key, blk.toRaw()); return; } @@ -526,7 +521,7 @@ TXDB.prototype.insert = async function insert(wtx, block) { const b = this.bucket(); const {tx, hash} = wtx; const height = block ? block.height : -1; - const details = new Details(this, wtx, block); + const details = new Details(wtx, block); const state = new BalanceDelta(); let own = false; @@ -682,7 +677,7 @@ TXDB.prototype.confirm = async function confirm(wtx, block) { const b = this.bucket(); const {tx, hash} = wtx; const height = block.height; - const details = new Details(this, wtx, block); + const details = new Details(wtx, block); const state = new BalanceDelta(); wtx.setBlock(block); @@ -697,21 +692,18 @@ TXDB.prototype.confirm = async function confirm(wtx, block) { const input = tx.inputs[i]; const {hash, index} = input.prevout; - let credit = credits[i]; + let resolved = false; // There may be new credits available // that we haven't seen yet. - if (!credit) { + if (!credits[i]) { await this.removeInput(b, tx, i); - credit = await this.getCredit(hash, index); + const credit = await this.getCredit(hash, index); if (!credit) continue; - const path = await this.getPath(credit.coin); - assert(path); - // Add a spend record and undo coin // for the coin we now know is ours. // We don't need to remove the coin @@ -719,10 +711,11 @@ TXDB.prototype.confirm = async function confirm(wtx, block) { // first place. this.spendCredit(b, credit, tx, i); - state.coin(path, -1); - state.unconfirmed(path, -credit.coin.value); + credits[i] = credit; + resolved = true; } + const credit = credits[i]; const coin = credit.coin; assert(coin.height !== -1); @@ -732,6 +725,11 @@ TXDB.prototype.confirm = async function confirm(wtx, block) { details.setInput(i, path, coin); + if (resolved) { + state.coin(path, -1); + state.unconfirmed(path, -coin.value); + } + // We can now safely remove the credit // entirely, now that we know it's also // been removed on-chain. @@ -828,7 +826,7 @@ TXDB.prototype.erase = async function erase(wtx, block) { const b = this.bucket(); const {tx, hash} = wtx; const height = block ? block.height : -1; - const details = new Details(this, wtx, block); + const details = new Details(wtx, block); const state = new BalanceDelta(); if (!tx.isCoinbase()) { @@ -1015,7 +1013,7 @@ TXDB.prototype.unconfirm = async function unconfirm(hash) { TXDB.prototype.disconnect = async function disconnect(wtx, block) { const b = this.bucket(); const {tx, hash, height} = wtx; - const details = new Details(this, wtx, block); + const details = new Details(wtx, block); const state = new BalanceDelta(); assert(block); @@ -1780,9 +1778,9 @@ TXDB.prototype.getCoinView = async function getCoinView(tx) { if (tx.isCoinbase()) return view; - for (const input of tx.inputs) { - const prevout = input.prevout; - const coin = await this.getCoin(prevout.hash, prevout.index); + for (const {prevout} of tx.inputs) { + const {hash, index} = prevout; + const coin = await this.getCoin(hash, index); if (!coin) continue; @@ -1881,7 +1879,7 @@ TXDB.prototype.toDetails = async function toDetails(wtxs) { TXDB.prototype._toDetails = async function _toDetails(wtx) { const tx = wtx.tx; const block = wtx.getBlock(); - const details = new Details(this, wtx, block); + const details = new Details(wtx, block); const coins = await this.getSpentCoins(tx); for (let i = 0; i < tx.inputs.length; i++) { @@ -2083,8 +2081,8 @@ TXDB.prototype.zap = async function zap(acct, age) { assert(now - wtx.mtime >= age); - this.logger.debug('Zapping TX: %s (%s)', - wtx.tx.txid(), this.id); + this.logger.debug('Zapping TX: %s (%d)', + wtx.tx.txid(), this.wid); await this.remove(wtx.hash); @@ -2365,13 +2363,9 @@ Credit.fromTX = function fromTX(tx, index, height) { * @param {TX} tx */ -function Details(txdb, wtx, block) { +function Details(wtx, block) { if (!(this instanceof Details)) - return new Details(txdb, wtx, block); - - this.wid = txdb.wid; - this.id = txdb.id; - this.tip = txdb.wdb.state.height; + return new Details(wtx, block); this.hash = wtx.hash; this.tx = wtx.tx; @@ -2452,11 +2446,14 @@ Details.prototype.setOutput = function setOutput(i, path) { * @returns {Number} */ -Details.prototype.getDepth = function getDepth() { +Details.prototype.getDepth = function getDepth(height) { if (this.height === -1) return 0; - const depth = this.tip - this.height; + if (height == null) + return 0; + + const depth = height - this.height; if (depth < 0) return 0; @@ -2503,13 +2500,11 @@ Details.prototype.getRate = function getRate(fee) { * @returns {Object} */ -Details.prototype.toJSON = function toJSON(network) { +Details.prototype.toJSON = function toJSON(network, height) { const fee = this.getFee(); const rate = this.getRate(fee); return { - wid: this.wid, - id: this.id, hash: util.revHex(this.hash), height: this.height, block: this.block ? util.revHex(this.block) : null, @@ -2521,7 +2516,7 @@ Details.prototype.toJSON = function toJSON(network) { virtualSize: this.vsize, fee: fee, rate: rate, - confirmations: this.getDepth(), + confirmations: this.getDepth(height), inputs: this.inputs.map((input) => { return input.getJSON(network); }), diff --git a/lib/wallet/wallet.js b/lib/wallet/wallet.js index 37151746..fc49d737 100644 --- a/lib/wallet/wallet.js +++ b/lib/wallet/wallet.js @@ -895,7 +895,7 @@ Wallet.prototype.hasAddress = async function hasAddress(address) { Wallet.prototype.getPath = async function getPath(address) { const hash = Address.getHash(address, 'hex'); - return this.wdb.getPath(this.wid, this.id, hash); + return this.wdb.getPath(this.wid, hash); }; /** @@ -907,7 +907,7 @@ Wallet.prototype.getPath = async function getPath(address) { Wallet.prototype.readPath = async function readPath(address) { const hash = Address.getHash(address, 'hex'); - return this.wdb.readPath(this.wid, this.id, hash); + return this.wdb.readPath(this.wid, hash); }; /** @@ -935,7 +935,6 @@ Wallet.prototype.getPaths = async function getPaths(acct) { const result = []; for (const path of paths) { - path.id = this.id; path.name = await this.getAccountName(path.account); assert(path.name); diff --git a/lib/wallet/walletdb.js b/lib/wallet/walletdb.js index a86395f8..34aea36b 100644 --- a/lib/wallet/walletdb.js +++ b/lib/wallet/walletdb.js @@ -80,6 +80,7 @@ function WalletDB(options) { } this.state = new ChainState(); + this.height = 0; this.wallets = new Map(); this.depth = 0; this.rescanning = false; @@ -303,6 +304,7 @@ WalletDB.prototype.init = async function init() { if (cache) { this.state = cache; + this.height = cache.height; return; } @@ -337,6 +339,7 @@ WalletDB.prototype.init = async function init() { await b.write(); this.state = state; + this.height = state.height; }; /** @@ -827,7 +830,6 @@ WalletDB.prototype._rename = async function _rename(wallet, id) { b.del(layout.l(old)); wallet.id = id; - wallet.txdb.id = id; this.save(b, wallet); @@ -1085,8 +1087,8 @@ WalletDB.prototype.savePath = async function savePath(b, wid, path) { * @returns {Promise} */ -WalletDB.prototype.getPath = async function getPath(wid, id, hash) { - const path = await this.readPath(wid, id, hash); +WalletDB.prototype.getPath = async function getPath(wid, hash) { + const path = await this.readPath(wid, hash); if (!path) return null; @@ -1104,15 +1106,13 @@ WalletDB.prototype.getPath = async function getPath(wid, id, hash) { * @returns {Promise} */ -WalletDB.prototype.readPath = async function readPath(wid, id, hash) { +WalletDB.prototype.readPath = async function readPath(wid, hash) { const data = await this.db.get(layout.P(wid, hash)); if (!data) return null; const path = Path.fromRaw(data); - path.id = id; - path.wid = wid; path.hash = hash; return path; @@ -1206,7 +1206,6 @@ WalletDB.prototype.getWalletPaths = async function getWalletPaths(wid) { const path = Path.fromRaw(item.value); path.hash = hash; - path.wid = wid; paths.push(path); } @@ -1453,6 +1452,7 @@ WalletDB.prototype.syncState = async function syncState(tip) { await b.write(); this.state = state; + this.height = state.height; }; /** @@ -1472,6 +1472,7 @@ WalletDB.prototype.markState = async function markState(block) { await b.write(); this.state = state; + this.height = state.height; }; /** diff --git a/lib/wallet/walletkey.js b/lib/wallet/walletkey.js index 73381e54..d8b0a14c 100644 --- a/lib/wallet/walletkey.js +++ b/lib/wallet/walletkey.js @@ -26,8 +26,6 @@ function WalletKey(options, network) { this.keyType = Path.types.HD; - this.id = null; - this.wid = -1; this.name = null; this.account = -1; this.branch = -1; @@ -121,8 +119,6 @@ WalletKey.fromSecret = function fromSecret(data, network) { WalletKey.prototype.toJSON = function toJSON() { return { network: this.network.type, - wid: this.wid, - id: this.id, name: this.name, account: this.account, branch: this.branch, @@ -169,8 +165,6 @@ WalletKey.fromRaw = function fromRaw(data) { WalletKey.prototype.fromHD = function fromHD(account, key, branch, index) { this.keyType = Path.types.HD; - this.id = account.id; - this.wid = account.wid; this.name = account.name; this.account = account.accountIndex; this.branch = branch; @@ -207,8 +201,6 @@ WalletKey.fromHD = function fromHD(account, key, branch, index) { WalletKey.prototype.fromImport = function fromImport(account, data) { this.keyType = Path.types.KEY; - this.id = account.id; - this.wid = account.wid; this.name = account.name; this.account = account.accountIndex; this.witness = account.witness; @@ -236,8 +228,6 @@ WalletKey.fromImport = function fromImport(account, data) { WalletKey.prototype.fromRing = function fromRing(account, ring) { this.keyType = Path.types.KEY; - this.id = account.id; - this.wid = account.wid; this.name = account.name; this.account = account.accountIndex; this.witness = account.witness; @@ -263,8 +253,6 @@ WalletKey.fromRing = function fromRing(account, ring) { WalletKey.prototype.toPath = function toPath() { const path = new Path(); - path.id = this.id; - path.wid = this.wid; path.name = this.name; path.account = this.account; diff --git a/test/wallet-test.js b/test/wallet-test.js index 6fd301db..d7245528 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -1318,7 +1318,8 @@ describe('Wallet', function() { const details = await wallet.toDetails(txs); - assert.strictEqual(details[0].toJSON().id, 'test'); + assert(details.length > 0); + assert.strictEqual(wallet.id, 'test'); }); it('should change passphrase with encrypted imports', async () => {