diff --git a/bin/cli b/bin/cli index 9d3f3967..94a7c6f8 100755 --- a/bin/cli +++ b/bin/cli @@ -320,6 +320,12 @@ CLI.prototype.getDetails = co(function* getDetails() { this.log(details); }); +CLI.prototype.getWalletBlock = co(function* getWalletBlock() { + var height = this.argv[0] | 0; + var details = yield this.wallet.getBlock(height); + this.log(details); +}); + CLI.prototype.retoken = co(function* retoken() { var result = yield this.wallet.retoken(); this.log(result); @@ -480,6 +486,8 @@ CLI.prototype.handleWallet = co(function* handleWallet() { return yield this.zapWallet(); case 'tx': return yield this.getDetails(); + case 'block': + return yield this.getWalletBlock(); case 'view': return yield this.viewTX(); case 'import': @@ -518,6 +526,7 @@ CLI.prototype.handleWallet = co(function* handleWallet() { this.log(' $ sign [tx-hex]: Sign transaction.'); this.log(' $ zap [age?]: Zap pending wallet TXs.'); this.log(' $ tx [hash]: View transaction details.'); + this.log(' $ block [height]: View wallet block.'); this.log(' $ view [tx-hex]: Parse and view transaction.'); this.log(' $ import [wif|hex]: Import private or public key.'); this.log(' $ watch [address]: Import an address.'); diff --git a/lib/http/client.js b/lib/http/client.js index 476ca86e..486c5b40 100644 --- a/lib/http/client.js +++ b/lib/http/client.js @@ -433,7 +433,7 @@ HTTPClient.prototype.none = function none() { * Request the raw wallet JSON (will create wallet if it does not exist). * @private * @param {Object} options - See {@link Wallet}. - * @returns {Promise} - Returns Object. + * @returns {Promise} */ HTTPClient.prototype.createWallet = function createWallet(options) { @@ -444,7 +444,7 @@ HTTPClient.prototype.createWallet = function createWallet(options) { * Get the raw wallet JSON. * @private * @param {WalletID} id - * @returns {Promise} - Returns Object. + * @returns {Promise} */ HTTPClient.prototype.getWallet = function getWallet(id) { @@ -454,7 +454,7 @@ HTTPClient.prototype.getWallet = function getWallet(id) { /** * Get wallet transaction history. * @param {WalletID} id - * @returns {Promise} - Returns {@link TX}[]. + * @returns {Promise} */ HTTPClient.prototype.getHistory = function getHistory(id, account) { @@ -465,7 +465,7 @@ HTTPClient.prototype.getHistory = function getHistory(id, account) { /** * Get wallet coins. * @param {WalletID} id - * @returns {Promise} - Returns {@link Coin}[]. + * @returns {Promise} */ HTTPClient.prototype.getCoins = function getCoins(id, account) { @@ -476,7 +476,7 @@ HTTPClient.prototype.getCoins = function getCoins(id, account) { /** * Get all unconfirmed transactions. * @param {WalletID} id - * @returns {Promise} - Returns {@link TX}[]. + * @returns {Promise} */ HTTPClient.prototype.getPending = function getPending(id, account) { @@ -487,7 +487,7 @@ HTTPClient.prototype.getPending = function getPending(id, account) { /** * Calculate wallet balance. * @param {WalletID} id - * @returns {Promise} - Returns {@link Balance}. + * @returns {Promise} */ HTTPClient.prototype.getBalance = function getBalance(id, account) { @@ -499,7 +499,7 @@ HTTPClient.prototype.getBalance = function getBalance(id, account) { * Get last N wallet transactions. * @param {WalletID} id * @param {Number} limit - Max number of transactions. - * @returns {Promise} - Returns {@link TX}[]. + * @returns {Promise} */ HTTPClient.prototype.getLast = function getLast(id, account, limit) { @@ -515,7 +515,7 @@ HTTPClient.prototype.getLast = function getLast(id, account, limit) { * @param {Number} options.end - End time. * @param {Number?} options.limit - Max number of records. * @param {Boolean?} options.reverse - Reverse order. - * @returns {Promise} - Returns {@link TX}[]. + * @returns {Promise} */ HTTPClient.prototype.getRange = function getRange(id, account, options) { @@ -534,20 +534,31 @@ HTTPClient.prototype.getRange = function getRange(id, account, options) { * is available in the wallet history). * @param {WalletID} id * @param {Hash} hash - * @returns {Promise} - Returns {@link TX}[]. + * @returns {Promise} */ HTTPClient.prototype.getWalletTX = function getWalletTX(id, hash) { return this._get('/wallet/' + id + '/tx/' + hash); }; +/** + * Get wallet block. + * @param {WalletID} id + * @param {Number} height + * @returns {Promise} + */ + +HTTPClient.prototype.getWalletBlock = function getWalletBlock(id, height) { + return this._get('/wallet/' + id + '/block/' + height); +}; + /** * Get unspent coin (only possible if the transaction * is available in the wallet history). * @param {WalletID} id * @param {Hash} hash * @param {Number} index - * @returns {Promise} - Returns {@link Coin}[]. + * @returns {Promise} */ HTTPClient.prototype.getWalletCoin = function getWalletCoin(id, account, hash, index) { @@ -562,7 +573,7 @@ HTTPClient.prototype.getWalletCoin = function getWalletCoin(id, account, hash, i * @param {Object} options * @param {Base58Address} options.address * @param {Amount} options.value - * @returns {Promise} - Returns {@link TX}. + * @returns {Promise} */ HTTPClient.prototype.send = function send(id, options) { @@ -611,7 +622,7 @@ HTTPClient.prototype.setPassphrase = function setPassphrase(id, old, new_) { * Create a transaction, fill. * @param {WalletID} id * @param {Object} options - * @returns {Promise} - Returns {@link TX}. + * @returns {Promise} */ HTTPClient.prototype.createTX = function createTX(id, options) { @@ -636,7 +647,7 @@ HTTPClient.prototype.createTX = function createTX(id, options) { * @param {WalletID} id * @param {TX} tx * @param {Object} options - * @returns {Promise} - Returns {@link TX}. + * @returns {Promise} */ HTTPClient.prototype.sign = function sign(id, tx, options) { @@ -654,7 +665,7 @@ HTTPClient.prototype.sign = function sign(id, tx, options) { /** * Fill a transaction with coins. * @param {TX} tx - * @returns {Promise} - Returns {@link TX}. + * @returns {Promise} */ HTTPClient.prototype.fillCoins = function fillCoins(id, tx) { @@ -895,7 +906,7 @@ HTTPClient.prototype.createAccount = function createAccount(id, options) { * Create address. * @param {WalletID} id * @param {Object} options - * @returns {Promise} - Returns Array. + * @returns {Promise} */ HTTPClient.prototype.createAddress = function createAddress(id, options) { @@ -916,7 +927,7 @@ HTTPClient.prototype.createAddress = function createAddress(id, options) { * Create address. * @param {WalletID} id * @param {Object} options - * @returns {Promise} - Returns Array. + * @returns {Promise} */ HTTPClient.prototype.createNested = function createNested(id, options) { diff --git a/lib/http/server.js b/lib/http/server.js index e0971eb2..f4aa0695 100644 --- a/lib/http/server.js +++ b/lib/http/server.js @@ -917,6 +917,21 @@ HTTPServer.prototype._init = function _init() { send(200, { success: true }); })); + // Get Block Record + this.get('/wallet/:id/block/:height', con(function* (req, res, send, next) { + var height = req.options.height; + var block; + + enforce(height != null, 'Height is required.'); + + block = yield req.wallet.getBlock(height); + + if (!block) + return send(404); + + send(200, block.toJSON()); + })); + // Add key this.put('/wallet/:id/shared-key', con(function* (req, res, send, next) { var options = req.options; diff --git a/lib/http/wallet.js b/lib/http/wallet.js index f0e540a4..b7d6f805 100644 --- a/lib/http/wallet.js +++ b/lib/http/wallet.js @@ -198,6 +198,14 @@ HTTPWallet.prototype.getTX = function getTX(hash) { return this.client.getWalletTX(this.id, hash); }; +/** + * @see Wallet#getBlock + */ + +HTTPWallet.prototype.getBlock = function getBlock(height) { + return this.client.getWalletBlock(this.id, height); +}; + /** * @see Wallet#getCoin */ diff --git a/lib/wallet/txdb.js b/lib/wallet/txdb.js index 5ddabce8..d7548f9f 100644 --- a/lib/wallet/txdb.js +++ b/lib/wallet/txdb.js @@ -981,37 +981,38 @@ TXDB.prototype.removeBlockMap = co(function* removeBlockMap(tx, height) { */ TXDB.prototype.getBlock = co(function* getBlock(height) { - var data = yield this.db.get(layout.b(height)); + var data = yield this.get(layout.b(height)); + if (!data) return; + return BlockRecord.fromRaw(data); }); /** * Append to the global block record. * @param {TX} tx - * @param {Number} height + * @param {BlockMeta} entry * @returns {Promise} */ TXDB.prototype.addBlock = co(function* addBlock(tx, entry) { var hash = tx.hash('hex'); - var block = yield this.getBlock(entry.height); + var height = tx.height; + var block = yield this.getBlock(height); if (!block) - block = new BlockRecord(entry.hash, entry.height, entry.ts); + block = new BlockRecord(tx.block, tx.height, tx.ts); - if (block.hashes.indexOf(hash) !== -1) + if (!block.add(hash)) return; - block.hashes.push(hash); - - this.put(layout.b(entry.height), block.toRaw()); + this.put(layout.b(height), block.toRaw()); }); /** * Remove from the global block record. - * @param {TX} + * @param {TX} tx * @param {Number} height * @returns {Promise} */ @@ -1019,18 +1020,13 @@ TXDB.prototype.addBlock = co(function* addBlock(tx, entry) { TXDB.prototype.removeBlock = co(function* removeBlock(tx, height) { var hash = tx.hash('hex'); var block = yield this.getBlock(height); - var index; if (!block) return; - index = block.hashes.indexOf(hash); - - if (index === -1) + if (!block.remove(hash)) return; - block.hashes.splice(index, 1); - if (block.hashes.length === 0) { this.del(layout.b(height)); return; @@ -1043,6 +1039,7 @@ TXDB.prototype.removeBlock = co(function* removeBlock(tx, height) { * Add transaction, potentially runs * `confirm()` and `removeConflicts()`. * @param {TX} tx + * @param {BlockMeta} block * @returns {Promise} */ @@ -1127,6 +1124,7 @@ TXDB.prototype._add = co(function* add(tx, block) { * Insert transaction. * @private * @param {TX} tx + * @param {BlockMeta} block * @returns {Promise} */ @@ -1267,8 +1265,10 @@ TXDB.prototype.insert = co(function* insert(tx, block) { this.put(layout.H(account, tx.height, hash), DUMMY); } - if (tx.height !== -1) + if (tx.height !== -1) { yield this.addBlockMap(tx, tx.height); + yield this.addBlock(tx, block); + } // Update the transaction counter and // commit the new state. This state will @@ -1296,6 +1296,7 @@ TXDB.prototype.insert = co(function* insert(tx, block) { * Attempt to confirm a transaction. * @private * @param {TX} tx + * @param {BlockMeta} block * @returns {Promise} */ @@ -1333,6 +1334,7 @@ TXDB.prototype.confirm = co(function* confirm(hash, block) { * Attempt to confirm a transaction. * @private * @param {TX} tx + * @param {BlockMeta} block * @returns {Promise} */ @@ -1437,8 +1439,10 @@ TXDB.prototype._confirm = co(function* confirm(tx, block) { this.put(layout.H(account, tx.height, hash), DUMMY); } - if (tx.height !== -1) + if (tx.height !== -1) { yield this.addBlockMap(tx, tx.height); + yield this.addBlock(tx, block); + } // Commit the new state. The balance has updated. this.put(layout.R, this.pending.commit()); @@ -1567,8 +1571,10 @@ TXDB.prototype.erase = co(function* erase(tx) { this.del(layout.H(account, tx.height, hash)); } - if (tx.height !== -1) + if (tx.height !== -1) { yield this.removeBlockMap(tx, tx.height); + yield this.removeBlock(tx, tx.height); + } // Update the transaction counter // and commit new state due to @@ -1740,6 +1746,7 @@ TXDB.prototype.disconnect = co(function* disconnect(tx) { } yield this.removeBlockMap(tx, height); + yield this.removeBlock(tx, height); // We need to update the now-removed // block properties and reindex due @@ -3359,8 +3366,54 @@ function BlockRecord(hash, height, ts) { this.height = height != null ? height : -1; this.ts = ts || 0; this.hashes = []; + this.index = {}; } +/** + * Add transaction to block record. + * @param {Hash} hash + * @returns {Boolean} + */ + +BlockRecord.prototype.add = function add(hash) { + if (this.index[hash]) + return false; + + this.index[hash] = true; + this.hashes.push(hash); + + return true; +}; + +/** + * Remove transaction from block record. + * @param {Hash} hash + * @returns {Boolean} + */ + +BlockRecord.prototype.remove = function remove(hash) { + var index; + + if (!this.index[hash]) + return false; + + delete this.index[hash]; + + // Fast case + if (this.hashes[this.hashes.length - 1] === hash) { + this.hashes.pop(); + return true; + } + + index = this.hashes.indexOf(hash); + + assert(index !== -1); + + this.hashes.splice(index, 1); + + return true; +}; + /** * Instantiate wallet block from serialized tip data. * @private @@ -3369,12 +3422,17 @@ function BlockRecord(hash, height, ts) { BlockRecord.prototype.fromRaw = function fromRaw(data) { var p = new BufferReader(data); + var hash; + this.hash = p.readHash('hex'); this.height = p.readU32(); this.ts = p.readU32(); - while (p.left()) - this.hashes.push(p.readHash('hex')); + while (p.left()) { + hash = p.readHash('hex'); + this.index[hash] = true; + this.hashes.push(hash); + } return this; }; diff --git a/lib/wallet/wallet.js b/lib/wallet/wallet.js index cfa96d17..b18383da 100644 --- a/lib/wallet/wallet.js +++ b/lib/wallet/wallet.js @@ -1984,6 +1984,16 @@ Wallet.prototype.getTX = function getTX(hash) { return this.txdb.getTX(hash); }; +/** + * Get a block from the wallet. + * @param {Number} height + * @returns {Promise} - Returns {@link BlockRecord}. + */ + +Wallet.prototype.getBlock = function getBlock(height) { + return this.txdb.getBlock(height); +}; + /** * Add a transaction to the wallets TX history. * @param {TX} tx diff --git a/lib/wallet/walletdb.js b/lib/wallet/walletdb.js index eae1c802..c73857ce 100644 --- a/lib/wallet/walletdb.js +++ b/lib/wallet/walletdb.js @@ -476,7 +476,7 @@ WalletDB.prototype.wipe = co(function* wipe() { this.logger.warning('Wiping WalletDB TXDB...'); this.logger.warning('I hope you know what you\'re doing.'); - iter = db.iterator({ + iter = this.db.iterator({ gte: new Buffer([0x00]), lte: new Buffer([0xff]) }); @@ -2002,15 +2002,15 @@ WalletDB.prototype.addTX = co(function* addTX(tx) { block = yield this.getBlock(tx.height); if (!block) - throw new Error('WDB: Inserting confirmed transaction.'); + throw new Error('WDB: Cannot insert tx from unknown chain.'); if (tx.block !== block.hash) - throw new Error('WDB: Inserting confirmed transaction.'); + throw new Error('WDB: Cannot insert tx from alternate chain.'); this.logger.warning('WalletDB is inserting confirmed transaction.'); } - return yield this._insert(tx); + return yield this._insert(tx, block); } finally { unlock(); }