From 3b333c07cdd0deafda3d37bec8c261dbc9d5f955 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 24 Oct 2016 15:13:45 -0700 Subject: [PATCH] walletdb: start separating out walletdb. --- lib/chain/chaindb.js | 87 ++++++++---- lib/http/server.js | 44 +++++- lib/node/fullnode.js | 106 +++++++-------- lib/node/node.js | 20 ++- lib/node/spvnode.js | 54 +++----- lib/primitives/merkleblock.js | 76 ----------- lib/wallet/txdb.js | 11 +- lib/wallet/wallet.js | 15 +-- lib/wallet/walletdb.js | 243 ++++++++++++++++++++++++---------- 9 files changed, 374 insertions(+), 282 deletions(-) diff --git a/lib/chain/chaindb.js b/lib/chain/chaindb.js index d9ee3a9d..b8751757 100644 --- a/lib/chain/chaindb.js +++ b/lib/chain/chaindb.js @@ -479,6 +479,7 @@ ChainDB.prototype.getHash = co(function* getHash(height) { ChainDB.prototype.getChainHeight = co(function* getChainHeight() { var entry = yield this.getTip(); + if (!entry) return -1; @@ -676,7 +677,12 @@ ChainDB.prototype.getEntries = function getEntries() { */ ChainDB.prototype.getCoin = co(function* getCoin(hash, index) { - var coins = this.coinCache.get(hash); + var coins; + + if (this.options.spv) + return; + + coins = this.coinCache.get(hash); if (coins) return Coins.parseCoin(coins, hash, index); @@ -698,7 +704,12 @@ ChainDB.prototype.getCoin = co(function* getCoin(hash, index) { */ ChainDB.prototype.getCoins = co(function* getCoins(hash) { - var coins = this.coinCache.get(hash); + var coins; + + if (this.options.spv) + return; + + coins = this.coinCache.get(hash); if (coins) return Coins.fromRaw(coins, hash); @@ -776,10 +787,9 @@ ChainDB.prototype.getUndoCoins = co(function* getUndoCoins(hash) { */ ChainDB.prototype.getUndoView = co(function* getUndoView(block) { - var i, j, k, tx, input, coin, view, coins; - - view = yield this.getCoinView(block); - coins = yield this.getUndoCoins(block.hash()); + var view = yield this.getCoinView(block); + var coins = yield this.getUndoCoins(block.hash()); + var i, j, k, tx, input, coin; if (!coins) return view; @@ -810,8 +820,12 @@ ChainDB.prototype.getUndoView = co(function* getUndoView(block) { */ ChainDB.prototype.getBlock = co(function* getBlock(hash) { - var item = yield this.getBoth(hash); - var data, block; + var item, data, block; + + if (this.options.spv) + return; + + item = yield this.getBoth(hash); if (!item.hash) return; @@ -821,12 +835,6 @@ ChainDB.prototype.getBlock = co(function* getBlock(hash) { if (!data) return; - if (this.options.spv) { - block = MerkleBlock.fromFull(data); - block.setHeight(item.height); - return block; - } - block = Block.fromRaw(data); block.setHeight(item.height); @@ -840,7 +848,12 @@ ChainDB.prototype.getBlock = co(function* getBlock(hash) { */ ChainDB.prototype.getRawBlock = co(function* getRawBlock(block) { - var hash = yield this.getHash(block); + var hash; + + if (this.options.spv) + return; + + hash = yield this.getHash(block); if (!hash) return; @@ -857,9 +870,6 @@ ChainDB.prototype.getRawBlock = co(function* getRawBlock(block) { ChainDB.prototype.getFullBlock = co(function* getFullBlock(hash) { var block = yield this.getBlock(hash); - if (this.options.spv) - return block; - if (!block) return; @@ -1090,7 +1100,7 @@ ChainDB.prototype.getTXByAddress = co(function* getTXByAddress(addresses) { * Scan the blockchain for transactions containing specified address hashes. * @param {Hash} start - Block hash to start at. * @param {Hash[]} hashes - Address hashes. - * @param {Function} iter - Iterator. Accepts ({@link TX}, {@link Function}). + * @param {Function} iter - Iterator. * @returns {Promise} */ @@ -1358,6 +1368,28 @@ ChainDB.prototype.reset = co(function* reset(block) { } }); +/** + * Reset the chain to a height or hash. Useful for replaying + * the blockchain download for SPV. + * @param {Hash|Number} block - hash/height + * @returns {Promise} + */ + +ChainDB.prototype.replay = co(function* replay(block) { + var entry = yield this.get(block); + + if (!entry) + throw new Error('Block not found.'); + + if (!(yield entry.isMainChain())) + throw new Error('Cannot reset on alternate chain.'); + + if (entry.hash === this.network.genesis.hash) + return yield this.reset(entry.hash); + + yield this.reset(entry.prevBlock); +}); + /** * Save a block (not an entry) to the * database and potentially connect the inputs. @@ -1367,11 +1399,8 @@ ChainDB.prototype.reset = co(function* reset(block) { */ ChainDB.prototype.saveBlock = co(function* saveBlock(block, view) { - if (this.options.spv) { - this.put(layout.b(block.hash()), block.toFull()); - yield this.pruneBlock(block); + if (this.options.spv) return; - } this.put(layout.b(block.hash()), block.toRaw()); @@ -1389,7 +1418,12 @@ ChainDB.prototype.saveBlock = co(function* saveBlock(block, view) { */ ChainDB.prototype.removeBlock = co(function* removeBlock(hash) { - var block = yield this.getBlock(hash); + var block; + + if (this.options.spv) + return; + + block = yield this.getBlock(hash); if (!block) throw new Error('Block not found.'); @@ -1599,7 +1633,10 @@ ChainDB.prototype.disconnectBlock = co(function* disconnectBlock(block) { ChainDB.prototype.pruneBlock = co(function* pruneBlock(block) { var height, hash; - if (!this.options.prune && !this.options.spv) + if (this.options.spv) + return; + + if (!this.options.prune) return; height = block.height - this.network.block.keepBlocks; diff --git a/lib/http/server.js b/lib/http/server.js index 9c102b2b..091a5532 100644 --- a/lib/http/server.js +++ b/lib/http/server.js @@ -680,7 +680,7 @@ HTTPServer.prototype._init = function _init() { var fee; if (!this.fees) - return send(400, { error: 'Fee estimation not available.' }); + return send(200, { rate: utils.btc(this.network.feeRate) }); fee = this.fees.estimateFee(req.options.blocks); @@ -695,7 +695,7 @@ HTTPServer.prototype._init = function _init() { enforce(height != null, 'Hash or height is required.'); send(200, { success: true }); - yield this.node.scan(height); + yield this.walletdb.rescan(height); })); // Resend @@ -1244,7 +1244,42 @@ HTTPServer.prototype._initIO = function _initIO() { callback(); }); - socket.on('scan chain', function(args, callback) { + socket.on('estimate fee', function(args, callback) { + var blocks = args[0]; + var rate; + + if (blocks != null && !utils.isNumber(blocks)) + return callback({ error: 'Invalid parameter.' }); + + if (!self.fees) { + rate = self.network.feeRate; + rate = utils.btc(rate); + return callback(null, rate); + } + + rate = self.fees.estimateFee(blocks); + rate = utils.btc(rate); + + return callback(null, rate); + }); + + socket.on('send', function(args, callback) { + var data = args[0]; + var tx; + + if (!utils.isHex(data)) + return callback({ error: 'Invalid parameter.' }); + + try { + tx = TX.fromRaw(data, 'hex'); + } catch (e) { + return callback({ error: 'Invalid parameter.' }); + } + + self.node.send(tx); + }); + + socket.on('scan', function(args, callback) { var start = args[0]; if (!utils.isHex256(start) && !utils.isUInt32(start)) @@ -1402,6 +1437,7 @@ function ClientSocket(server, socket) { this.filterCount = 0; this.api = false; + this.node = this.server.node; this.chain = this.server.chain; this.mempool = this.server.mempool; this.pool = this.server.pool; @@ -1574,7 +1610,7 @@ ClientSocket.prototype.testFilter = function testFilter(tx) { ClientSocket.prototype.scan = co(function* scan(start) { var scanner = this.scanner.bind(this); - yield this.chain.db.scan(start, this.filter, scanner); + yield this.node.scan(start, this.filter, scanner); }); ClientSocket.prototype.scanner = function scanner(entry, txs) { diff --git a/lib/node/fullnode.js b/lib/node/fullnode.js index 9f37eccb..ab9dbbb3 100644 --- a/lib/node/fullnode.js +++ b/lib/node/fullnode.js @@ -28,7 +28,7 @@ try { /** * Create a fullnode complete with a chain, * mempool, miner, wallet, etc. - * @exports Fullnode + * @exports FullNode * @extends Node * @constructor * @param {Object?} options @@ -53,15 +53,15 @@ try { * @property {Miner} miner * @property {WalletDB} walletdb * @property {HTTPServer} http - * @emits Fullnode#block - * @emits Fullnode#tx - * @emits Fullnode#alert - * @emits Fullnode#error + * @emits FullNode#block + * @emits FullNode#tx + * @emits FullNode#alert + * @emits FullNode#error */ -function Fullnode(options) { - if (!(this instanceof Fullnode)) - return new Fullnode(options); +function FullNode(options) { + if (!(this instanceof FullNode)) + return new FullNode(options); Node.call(this, options); @@ -143,7 +143,7 @@ function Fullnode(options) { this.walletdb = new WalletDB({ network: this.network, logger: this.logger, - fees: this.fees, + client: this, db: this.options.db, location: this.location('walletdb'), witness: this.options.witness, @@ -175,14 +175,14 @@ function Fullnode(options) { this._init(); } -utils.inherits(Fullnode, Node); +utils.inherits(FullNode, Node); /** * Initialize the node. * @private */ -Fullnode.prototype._init = function _init() { +FullNode.prototype._init = function _init() { var self = this; var onError = this._error.bind(this); @@ -227,20 +227,16 @@ Fullnode.prototype._init = function _init() { this.miner.on('block', function(block) { self.broadcast(block.toInv()).catch(onError); }); - - this.walletdb.on('send', function(tx) { - self.sendTX(tx).catch(onError); - }); }; /** * Open the node and all its child objects, * wait for the database to load. - * @alias Fullnode#open + * @alias FullNode#open * @returns {Promise} */ -Fullnode.prototype._open = co(function* open() { +FullNode.prototype._open = co(function* open() { yield this.chain.open(); yield this.mempool.open(); yield this.miner.open(); @@ -250,12 +246,6 @@ Fullnode.prototype._open = co(function* open() { // Ensure primary wallet. yield this.openWallet(); - // Rescan for any missed transactions. - yield this.rescan(); - - // Rebroadcast pending transactions. - yield this.resend(); - if (this.http) yield this.http.open(); @@ -264,11 +254,11 @@ Fullnode.prototype._open = co(function* open() { /** * Close the node, wait for the database to close. - * @alias Fullnode#close + * @alias FullNode#close * @returns {Promise} */ -Fullnode.prototype._close = co(function* close() { +FullNode.prototype._close = co(function* close() { if (this.http) yield this.http.close(); @@ -286,25 +276,35 @@ Fullnode.prototype._close = co(function* close() { }); /** - * Rescan for any missed transactions. + * Watch address hashes (nop). + * @param {Hash[]} hashes * @returns {Promise} */ -Fullnode.prototype.rescan = function rescan() { - // Always rescan to make sure we didn't - // miss anything: there is no atomicity - // between the chaindb and walletdb. - return this.scan(); +FullNode.prototype.watchAddress = function watchAddress(hashes) { + return Promise.resolve(); }; /** * Rescan for any missed transactions. - * @param {Number} height + * @param {Hash} start - Block hash to start at. + * @param {Hash[]} hashes - Address hashes. + * @param {Function} iter - Iterator. * @returns {Promise} */ -Fullnode.prototype.scan = function scan(height) { - return this.walletdb.rescan(this.chain.db, height); +FullNode.prototype.scan = function scan(start, filter, iter) { + return this.chain.db.scan(start, filter, iter); +}; + +/** + * Esimate smart fee. + * @param {Number?} blocks + * @returns {Promise} + */ + +FullNode.prototype.estimateFee = function estimateFee(blocks) { + return Promise.resolve(this.fees.estimateFee(blocks)); }; /** @@ -315,20 +315,20 @@ Fullnode.prototype.scan = function scan(height) { * @returns {Promise} */ -Fullnode.prototype.broadcast = function broadcast(item, callback) { +FullNode.prototype.broadcast = function broadcast(item, callback) { return this.pool.broadcast(item, callback); }; /** * Verify a transaction, add it to the mempool, and broadcast. - * Safer than {@link Fullnode#broadcast}. + * Safer than {@link FullNode#broadcast}. * @example * node.sendTX(tx, callback); * node.sendTX(tx, true, callback); * @param {TX} tx */ -Fullnode.prototype.sendTX = co(function* sendTX(tx) { +FullNode.prototype.sendTX = co(function* sendTX(tx) { try { yield this.mempool.addTX(tx); } catch (err) { @@ -353,7 +353,7 @@ Fullnode.prototype.sendTX = co(function* sendTX(tx) { * the p2p network (accepts leech peers). */ -Fullnode.prototype.listen = function listen() { +FullNode.prototype.listen = function listen() { return this.pool.listen(); }; @@ -361,7 +361,7 @@ Fullnode.prototype.listen = function listen() { * Connect to the network. */ -Fullnode.prototype.connect = function connect() { +FullNode.prototype.connect = function connect() { return this.pool.connect(); }; @@ -369,7 +369,7 @@ Fullnode.prototype.connect = function connect() { * Start the blockchain sync. */ -Fullnode.prototype.startSync = function startSync() { +FullNode.prototype.startSync = function startSync() { return this.pool.startSync(); }; @@ -377,7 +377,7 @@ Fullnode.prototype.startSync = function startSync() { * Stop syncing the blockchain. */ -Fullnode.prototype.stopSync = function stopSync() { +FullNode.prototype.stopSync = function stopSync() { return this.pool.stopSync(); }; @@ -387,7 +387,7 @@ Fullnode.prototype.stopSync = function stopSync() { * @returns {Promise} - Returns {@link Block}. */ -Fullnode.prototype.getBlock = function getBlock(hash) { +FullNode.prototype.getBlock = function getBlock(hash) { return this.chain.db.getBlock(hash); }; @@ -397,7 +397,7 @@ Fullnode.prototype.getBlock = function getBlock(hash) { * @returns {Promise} - Returns {@link Block}. */ -Fullnode.prototype.getFullBlock = function getFullBlock(hash) { +FullNode.prototype.getFullBlock = function getFullBlock(hash) { return this.chain.db.getFullBlock(hash); }; @@ -409,7 +409,7 @@ Fullnode.prototype.getFullBlock = function getFullBlock(hash) { * @returns {Promise} - Returns {@link Coin}. */ -Fullnode.prototype.getCoin = function getCoin(hash, index) { +FullNode.prototype.getCoin = function getCoin(hash, index) { var coin = this.mempool.getCoin(hash, index); if (coin) @@ -428,7 +428,7 @@ Fullnode.prototype.getCoin = function getCoin(hash, index) { * @returns {Promise} - Returns {@link Coin}[]. */ -Fullnode.prototype.getCoinsByAddress = co(function* getCoinsByAddress(addresses) { +FullNode.prototype.getCoinsByAddress = co(function* getCoinsByAddress(addresses) { var coins = this.mempool.getCoinsByAddress(addresses); var i, blockCoins, coin, spent; @@ -452,7 +452,7 @@ Fullnode.prototype.getCoinsByAddress = co(function* getCoinsByAddress(addresses) * @returns {Promise} - Returns {@link TX}[]. */ -Fullnode.prototype.getTXByAddress = co(function* getTXByAddress(addresses) { +FullNode.prototype.getTXByAddress = co(function* getTXByAddress(addresses) { var mempool = this.mempool.getTXByAddress(addresses); var txs = yield this.chain.db.getTXByAddress(addresses); return mempool.concat(txs); @@ -464,7 +464,7 @@ Fullnode.prototype.getTXByAddress = co(function* getTXByAddress(addresses) { * @returns {Promise} - Returns {@link TX}. */ -Fullnode.prototype.getTX = function getTX(hash) { +FullNode.prototype.getTX = function getTX(hash) { var tx = this.mempool.getTX(hash); if (tx) @@ -479,7 +479,7 @@ Fullnode.prototype.getTX = function getTX(hash) { * @returns {Promise} - Returns Boolean. */ -Fullnode.prototype.hasTX = function hasTX(hash) { +FullNode.prototype.hasTX = function hasTX(hash) { if (this.mempool.hasTX(hash)) return Promise.resolve(true); @@ -493,7 +493,7 @@ Fullnode.prototype.hasTX = function hasTX(hash) { * @returns {Promise} - Returns Boolean. */ -Fullnode.prototype.isSpent = function isSpent(hash, index) { +FullNode.prototype.isSpent = function isSpent(hash, index) { if (this.mempool.isSpent(hash, index)) return Promise.resolve(true); @@ -507,7 +507,7 @@ Fullnode.prototype.isSpent = function isSpent(hash, index) { * @returns {Promise} - Returns {@link TX}. */ -Fullnode.prototype.fillCoins = function fillCoins(tx) { +FullNode.prototype.fillCoins = function fillCoins(tx) { return this.mempool.fillAllCoins(tx); }; @@ -518,7 +518,7 @@ Fullnode.prototype.fillCoins = function fillCoins(tx) { * @returns {Promise} - Returns {@link TX}. */ -Fullnode.prototype.fillHistory = function fillHistory(tx) { +FullNode.prototype.fillHistory = function fillHistory(tx) { return this.mempool.fillAllHistory(tx); }; @@ -528,7 +528,7 @@ Fullnode.prototype.fillHistory = function fillHistory(tx) { * @returns {Promise} - Returns {@link Confidence}. */ -Fullnode.prototype.getConfidence = function getConfidence(tx) { +FullNode.prototype.getConfidence = function getConfidence(tx) { return this.mempool.getConfidence(tx); }; @@ -536,4 +536,4 @@ Fullnode.prototype.getConfidence = function getConfidence(tx) { * Expose */ -module.exports = Fullnode; +module.exports = FullNode; diff --git a/lib/node/node.js b/lib/node/node.js index 6c6174e5..f51f826e 100644 --- a/lib/node/node.js +++ b/lib/node/node.js @@ -263,12 +263,26 @@ Node.prototype.openWallet = co(function* openWallet() { }); /** - * Resend all pending transactions. + * Get chain tip. * @returns {Promise} */ -Node.prototype.resend = function resend() { - return this.walletdb.resend(); +Node.prototype.getTip = function getTip() { + return Promise.resolve(this.chain.tip); +}; + +/** + * Send a transaction. Do not wait for promise. + * @param {TX} tx + * @returns {Promise} + */ + +Node.prototype.send = function send(tx) { + var onError = this._error.bind(this); + + this.sendTX(tx).catch(onError); + + return Promise.resolve(); }; /* diff --git a/lib/node/spvnode.js b/lib/node/spvnode.js index 44ddee51..9af7a277 100644 --- a/lib/node/spvnode.js +++ b/lib/node/spvnode.js @@ -83,6 +83,7 @@ function SPVNode(options) { this.walletdb = new WalletDB({ network: this.network, logger: this.logger, + client: this, db: this.options.db, location: this.location('walletdb'), witness: this.options.witness, @@ -151,14 +152,6 @@ SPVNode.prototype._init = function _init() { this.chain.on('disconnect', function(entry, block) { self.walletdb.removeBlock(entry).catch(onError); }); - - this.walletdb.on('path', function(path) { - self.pool.watch(path.hash, 'hex'); - }); - - this.walletdb.on('send', function(tx) { - self.sendTX(tx).catch(onError); - }); }; /** @@ -179,12 +172,6 @@ SPVNode.prototype._open = co(function* open(callback) { // Load bloom filter. yield this.openFilter(); - // Rescan for any missed transactions. - yield this.rescan(); - - // Rebroadcast pending transactions. - yield this.resend(); - if (this.http) yield this.http.open(); @@ -211,44 +198,41 @@ SPVNode.prototype._close = co(function* close() { }); /** - * Initialize p2p bloom filter for address watching. + * Watch address hashes. + * @param {Hash[]} hashes * @returns {Promise} */ -SPVNode.prototype.openFilter = co(function* openFilter() { - var hashes = yield this.walletdb.getAddressHashes(); +SPVNode.prototype.watchAddress = function watchAddress(hashes) { var i; - if (hashes.length > 0) - this.logger.info('Adding %d addresses to filter.', hashes.length); + this.logger.info('Adding %d addresses to filter.', hashes.length); for (i = 0; i < hashes.length; i++) this.pool.watch(hashes[i], 'hex'); -}); -/** - * Rescan for any missed transactions. - * Note that this will replay the blockchain sync. - * @returns {Promise} - */ - -SPVNode.prototype.rescan = function rescan() { - // Always replay the last block to make - // sure we didn't miss anything: there - // is no atomicity between the chaindb - // and walletdb. - return this.scan(); + return Promise.resolve(); }; /** * Scan for any missed transactions. * Note that this will replay the blockchain sync. - * @param {Number|Hash} height + * @param {Number|Hash} start * @returns {Promise} */ -SPVNode.prototype.scan = function scan(height) { - return this.walletdb.rescan(this.chain.db, height); +SPVNode.prototype.scan = function rescan(start) { + return this.chain.db.replay(start); +}; + +/** + * Estimate smart fee (returns network fee rate). + * @param {Number?} blocks + * @returns {Promise} + */ + +SPVNode.prototype.estimateFee = function estimateFee(blocks) { + return Promise.resolve(this.network.feeRate); }; /** diff --git a/lib/primitives/merkleblock.js b/lib/primitives/merkleblock.js index bbb56a71..eefda81a 100644 --- a/lib/primitives/merkleblock.js +++ b/lib/primitives/merkleblock.js @@ -410,82 +410,6 @@ MerkleBlock.fromRaw = function fromRaw(data, enc) { return new MerkleBlock().fromRaw(data); }; -/** - * Serialize the merkleblock. - * @param {String?} enc - Encoding, can be `'hex'` or null. - * @returns {Buffer|String} - */ - -MerkleBlock.prototype.toFull = function toFull(writer) { - var p = BufferWriter(writer); - var i, tx, index; - - this.toRaw(p); - - p.writeVarint(this.txs.length); - - for (i = 0; i < this.txs.length; i++) { - tx = this.txs[i]; - index = tx.index; - if (index === -1) - index = 0x7fffffff; - p.writeU32(index); - tx.toRaw(p); - } - - if (!writer) - p = p.render(); - - return p; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -MerkleBlock.prototype.fromFull = function fromFull(data) { - var p = BufferReader(data); - var i, count, index, tx, hash, txid; - - this.fromRaw(p); - - this._validPartial = true; - - count = p.readVarint(); - - for (i = 0; i < count; i++) { - index = p.readU32(); - if (index === 0x7fffffff) - index = -1; - tx = TX.fromRaw(p); - hash = tx.hash(); - txid = tx.hash('hex'); - tx.setBlock(this, index); - this.txs.push(tx); - if (index !== -1) { - this.matches.push(hash); - this.map[txid] = index; - } - } - - return this; -}; - -/** - * Instantiate a merkleblock from a serialized data. - * @param {Buffer} data - * @param {String?} enc - Encoding, can be `'hex'` or null. - * @returns {MerkleBlock} - */ - -MerkleBlock.fromFull = function fromFull(data, enc) { - if (typeof data === 'string') - data = new Buffer(data, enc); - return new MerkleBlock().fromFull(data); -}; - /** * Convert the block to an object suitable * for JSON serialization. Note that the hashes diff --git a/lib/wallet/txdb.js b/lib/wallet/txdb.js index b6adf01a..3e6169b1 100644 --- a/lib/wallet/txdb.js +++ b/lib/wallet/txdb.js @@ -905,14 +905,6 @@ TXDB.prototype.removeTXRecord = co(function* removeTXRecord(tx) { if (!map.remove(this.wallet.wid)) return; - if (tx.height !== -1) { - block = yield this.walletdb.getBlockMap(tx.height); - assert(block); - - if (block.remove(hash, this.wallet.wid)) - this.walletdb.writeBlockMap(this.wallet, tx.height, block); - } - if (map.wids.length === 0) { this.walletdb.unwriteTXMap(this.wallet, hash); return; @@ -1500,6 +1492,9 @@ TXDB.prototype.erase = co(function* erase(tx) { yield this.removeTXRecord(tx); + if (tx.height !== -1) + yield this.removeBlockRecord(tx, tx.height); + // Update the transaction counter // and commit new state due to // balance change. diff --git a/lib/wallet/wallet.js b/lib/wallet/wallet.js index 50d5d9c3..56b732cc 100644 --- a/lib/wallet/wallet.js +++ b/lib/wallet/wallet.js @@ -1366,14 +1366,10 @@ Wallet.prototype._fund = co(function* fund(tx, options) { coins = yield this.getCoins(options.account); - rate = this.network.feeRate; + rate = options.rate; - if (options.rate != null) { - rate = options.rate; - } else { - if (this.db.fees) - rate = this.db.fees.estimateFee(); - } + if (rate == null) + rate = yield this.db.estimateFee(); // Don't use any locked coins. coins = this.txdb.filterLocked(coins); @@ -1486,7 +1482,8 @@ Wallet.prototype._send = co(function* send(options, passphrase) { yield this.db.addTX(tx); this.logger.debug('Sending wallet tx (%s): %s', this.id, tx.rhash); - this.db.emit('send', tx); + + yield this.db.send(tx); return tx; }); @@ -1504,7 +1501,7 @@ Wallet.prototype.resend = co(function* resend() { this.logger.info('Rebroadcasting %d transactions.', txs.length); for (i = 0; i < txs.length; i++) - this.db.emit('send', txs[i]); + yield this.db.send(tx); return txs; }); diff --git a/lib/wallet/walletdb.js b/lib/wallet/walletdb.js index 9185cede..ae8cb578 100644 --- a/lib/wallet/walletdb.js +++ b/lib/wallet/walletdb.js @@ -162,10 +162,9 @@ function WalletDB(options) { AsyncObject.call(this); this.options = options; - this.prune = options.prune !== false; this.network = Network.get(options.network); - this.fees = options.fees; this.logger = options.logger || Logger.global; + this.client = options.client; this.tip = null; this.height = -1; @@ -242,6 +241,8 @@ WalletDB.prototype._open = co(function* open() { this.depth, this.height); yield this.loadFilter(); + yield this.connect(); + yield this.resend(); }); /** @@ -263,6 +264,175 @@ WalletDB.prototype._close = co(function* close() { yield this.db.close(); }); +/** + * Connect and sync with the chain server. + * @returns {Promise} + */ + +WalletDB.prototype.connect = co(function* connect() { + var unlock = yield this.txLock.lock(); + try { + return yield this._connect(); + } finally { + unlock(); + } +}); + +/** + * Connect and sync with the chain server (without a lock). + * @private + * @returns {Promise} + */ + +WalletDB.prototype._connect = co(function* connect() { + var hashes, tip, height; + + if (!this.client) + return; + + // yield this.client.connect(); + // yield this.client.watchChain(); + + hashes = yield this.getHashes(); + + yield this.watchAddress(hashes); + + if (this.options.noScan) { + tip = yield this.client.getTip(); + + if (!tip) + throw new Error('Could not get chain tip.'); + + yield this.forceTip(tip); + + return; + } + + assert(this.network.block.keepBlocks > 36); + + height = this.height - 36; + + if (height < 0) + height = 0; + + yield this.scan(height, hashes); +}); + +/** + * Force a rescan. + * @param {ChainClient} chain + * @param {Number} height + * @returns {Promise} + */ + +WalletDB.prototype.rescan = co(function* rescan(height) { + var unlock = yield this.txLock.lock(); + try { + return yield this._rescan(height); + } finally { + unlock(); + } +}); + +/** + * Force a rescan (without a lock). + * @private + * @param {Number} height + * @returns {Promise} + */ + +WalletDB.prototype._rescan = co(function* rescan(height) { + var hashes; + + if (!this.client) + return; + + assert(utils.isNumber(height), 'Must pass in a height.'); + + hashes = yield this.getHashes(); + + yield this.watchAddress(hashes); + + yield this.scan(height, hashes); +}); + +/** + * Sync with the chain server (without a lock). + * @private + * @param {Number} height + * @param {Hashes[]} hashes + * @returns {Promise} + */ + +WalletDB.prototype.scan = co(function* sync(height, hashes) { + var self = this; + var blocks; + + if (!this.client) + return; + + assert(utils.isNumber(height), 'Must pass in a height.'); + + blocks = this.height - height; + + if (blocks < 0) + throw new Error('Cannot rescan future blocks.'); + + if (blocks > this.network.block.keepBlocks) + throw new Error('Cannot roll back beyond keepBlocks.'); + + yield this.rollback(height); + + this.logger.info('Scanning for %d addresses.', hashes.length); + + yield this.client.scan(this.tip.hash, hashes, function(block, txs) { + return self._addBlock(block, txs); + }); +}); + +/** + * Add addresses to chain server filter. + * @param {Hashes[]} hashes + * @returns {Promise} + */ + +WalletDB.prototype.watchAddress = co(function* watchAddress(hashes) { + if (!this.client) { + this.emit('watch address', hashes); + return; + } + + yield this.client.watchAddress(hashes); +}); + +/** + * Broadcast a transaction via chain server. + * @param {TX} tx + * @returns {Promise} + */ + +WalletDB.prototype.send = co(function* send(tx) { + if (!this.client) { + this.emit('send', tx); + return; + } + + yield this.client.send(tx); +}); + +/** + * Estimate smart fee from chain server. + * @param {Number} blocks + * @returns {Promise} + */ + +WalletDB.prototype.estimateFee = co(function* estimateFee(blocks) { + if (!this.client) + return this.network.feeRate; + + return yield this.client.estimateFee(blocks); +}); + /** * Backup the wallet db. * @param {String} path @@ -991,7 +1161,7 @@ WalletDB.prototype.savePath = co(function* savePath(wallet, path) { this.addFilter(hash); - this.emit('path', path); + yield this.watchAddress([path.hash]); map = yield this.getPathMap(hash); @@ -1173,71 +1343,6 @@ WalletDB.prototype.decryptKeys = co(function* decryptKeys(wallet, key) { } }); -/** - * Rescan the blockchain. - * @param {ChainDB} chaindb - * @param {Number} height - * @returns {Promise} - */ - -WalletDB.prototype.rescan = co(function* rescan(chaindb, height) { - var unlock = yield this.txLock.lock(); - try { - return yield this._rescan(chaindb, height); - } finally { - unlock(); - } -}); - -/** - * Rescan the blockchain without a lock. - * @private - * @param {ChainDB} chaindb - * @param {Number} height - * @returns {Promise} - */ - -WalletDB.prototype._rescan = co(function* rescan(chaindb, height) { - var self = this; - var tip, hashes, blocks; - - if (this.options.noScan) { - tip = yield chaindb.getTip(); - - if (!tip) - throw new Error('Could not get chain tip.'); - - yield this.forceTip(tip); - - return; - } - - if (height == null) { - assert(this.network.block.keepBlocks > 36); - height = this.height - 36; - if (height < 0) - height = 0; - } - - blocks = this.height - height; - - if (blocks < 0) - throw new Error('Cannot rescan future blocks.'); - - if (blocks > this.network.block.keepBlocks) - throw new Error('Cannot roll back beyond keepBlocks.'); - - yield this.rollback(height); - - hashes = yield this.getHashes(); - - this.logger.info('Scanning for %d addresses.', hashes.length); - - yield chaindb.scan(this.tip.hash, hashes, function(block, txs) { - return self._addBlock(block, txs); - }); -}); - /** * Get keys of all pending transactions * in the wallet db (for resending). @@ -1351,7 +1456,7 @@ WalletDB.prototype.resend = co(function* resend() { if (tx.isCoinbase()) continue; - this.emit('send', tx); + yield this.send(tx); } });