diff --git a/lib/bcoin/ldb.js b/lib/bcoin/ldb.js index 90ebeac1..f71c6fc8 100644 --- a/lib/bcoin/ldb.js +++ b/lib/bcoin/ldb.js @@ -145,12 +145,12 @@ ldb.parseOptions = function parseOptions(options) { */ ldb.destroy = function destroy(options, callback) { - options = ldb.parseOptions(options); + var backend = ldb.parseOptions(options).db; - if (!options.db.destroy) + if (!backend.destroy) return utils.nextTick(callback); - options.db.backend.destroy(options.location, callback); + backend.destroy(options.location, callback); }; /** @@ -160,12 +160,12 @@ ldb.destroy = function destroy(options, callback) { */ ldb.repair = function repair(options, callback) { - options = ldb.parseOptions(options); + var backend = ldb.parseOptions(options).db; - if (!options.db.backend.repair) + if (!backend.repair) return utils.asyncify(callback)(new Error('Cannot repair.')); - options.db.backend.repair(options.location, callback); + backend.repair(options.location, callback); }; /* diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 30a4e33a..7364c858 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -71,15 +71,19 @@ function Wallet(options) { this.id = options.id || null; this.master = options.master || null; - this.accountKey = options.accountKey || null; this.witness = options.witness || false; this.loaded = false; + this.loading = false; + this.accountKey = options.accountKey || null; this.accountIndex = options.accountIndex || 0; this.receiveDepth = options.receiveDepth || 1; this.changeDepth = options.changeDepth || 1; + this.receiveAddress = null; + this.changeAddress = null; + this.lookahead = options.lookahead != null ? options.lookahead : 5; - this.initialized = false; + this.initialized = options.initialized || false; this.type = options.type || 'pubkeyhash'; this.compressed = options.compressed !== false; @@ -107,6 +111,9 @@ function Wallet(options) { if (!this.id) this.id = this.getID(); + if (options.passphrase) + this.master.encrypt(options.passphrase); + // Non-alphanumeric IDs will break leveldb sorting. assert(/^[a-zA-Z0-9]+$/.test(this.id), 'Wallet IDs must be alphanumeric.'); @@ -120,6 +127,80 @@ function Wallet(options) { utils.inherits(Wallet, EventEmitter); +/** + * Open the wallet, register with the database. + * @param {Function} callback + */ + +Wallet.prototype.open = function open(callback) { + var self = this; + + callback = utils.ensure(callback); + + if (this.loaded) + return utils.nextTick(callback); + + if (this.loading) + return this.once('open', callback); + + this.loading = true; + + try { + this.db.register(this); + } catch (e) { + this.emit('error', err); + return callback(err); + } + + this.init(function(err) { + if (err) { + self.emit('error', err); + return callback(err); + } + + self.loading = false; + self.loaded = true; + self.emit('open'); + + return callback(); + }); +}; + +/** + * Close the wallet, unregister with the database. + * @method + * @param {Function} callback + */ + +Wallet.prototype.close = +Wallet.prototype.destroy = function destroy(callback) { + callback = utils.ensure(callback); + + if (!this.loaded) + return utils.nextTick(callback); + + assert(!this.loading); + + try { + this.db.unregister(this); + } catch (e) { + this.emit('error', err); + return callback(err); + } + + this.loaded = false; + + return utils.nextTick(callback); +}; + +/** + * Attempt to intialize the wallet (generating + * the first addresses along with the lookahead + * addresses). Called automatically from the + * walletdb and open(). + * @param {Function} callback + */ + Wallet.prototype.init = function init(callback) { var self = this; var addresses = []; @@ -499,8 +580,6 @@ Wallet.prototype.deriveAddress = function deriveAddress(change, index) { address = new bcoin.keyring(options); - this.emit('add address', address); - this.cache.set(data.path, address); return address; @@ -552,10 +631,7 @@ Wallet.prototype.createPath = function createPath(change, index) { /** * Parse a path. * @param {String} path - * @returns {Object} { - * path: String, - * change: Boolean, index: Number - * } + * @returns {Object} Contains `path`, `change`, and `index`. */ Wallet.prototype.parsePath = function parsePath(path) { @@ -678,7 +754,8 @@ Wallet.prototype.fill = function fill(tx, options, callback) { if (!options) options = {}; - assert(this.initialized); + if (!this.initialized) + return callback(new Error('Cannot use uninitialized wallet.')); this.getCoins(function(err, coins) { if (err) @@ -845,8 +922,6 @@ Wallet.prototype.deriveInputs = function deriveInputs(tx, callback) { */ Wallet.prototype.getPath = function getPath(address, callback) { - if (!address || typeof address !== 'string') - return callback(); this.db.getPath(this.id, address, callback); }; @@ -863,6 +938,9 @@ Wallet.prototype.getInputPaths = function getInputPaths(tx, callback) { var i, input, address, path; if (tx instanceof bcoin.input) { + if (!tx.coin) + return callback(new Error('Not all coins available.')); + return this.getPath(tx.coin.getHash(), function(err, path) { if (err) return callback(err); @@ -891,7 +969,7 @@ Wallet.prototype.getInputPaths = function getInputPaths(tx, callback) { }); }, function(err) { if (err) - return next(err); + return callback(err); return callback(null, utils.uniq(paths)); }); }; @@ -1487,8 +1565,9 @@ Wallet.prototype.__defineGetter__('address', function() { Wallet.prototype.inspect = function inspect() { return { id: this.id, - type: this.type, network: this.network.type, + initialized: this.initialized, + type: this.type, m: this.m, n: this.n, keyAddress: this.initialized @@ -1525,6 +1604,7 @@ Wallet.prototype.toJSON = function toJSON() { name: 'wallet', network: this.network.type, id: this.id, + initialized: this.initialized, type: this.type, m: this.m, n: this.n, @@ -1558,6 +1638,7 @@ Wallet.parseJSON = function parseJSON(json) { return { network: json.network, id: json.id, + initialized: json.initialized, type: json.type, m: json.m, n: json.n, @@ -1573,12 +1654,18 @@ Wallet.parseJSON = function parseJSON(json) { }; }; +/** + * Serialize the wallet. + * @returns {Buffer} + */ + Wallet.prototype.toRaw = function toRaw(writer) { var p = new BufferWriter(writer); var i; p.writeU32(this.network.magic); p.writeVarString(this.id, 'utf8'); + p.writeU8(this.initialized ? 1 : 0); p.writeU8(this.type === 'pubkeyhash' ? 0 : 1); p.writeU8(this.m); p.writeU8(this.n); @@ -1599,14 +1686,19 @@ Wallet.prototype.toRaw = function toRaw(writer) { return p; }; -Wallet.fromRaw = function fromRaw(data) { - return new Wallet(Wallet.parseRaw(data)); -}; +/** + * Parse a serialized wallet. Return a "naked" + * wallet object, suitable for passing into + * the wallet constructor. + * @param {Buffer} data + * @returns {Object} + */ Wallet.parseRaw = function parseRaw(data) { var p = new BufferReader(data); var network = bcoin.network.fromMagic(p.readU32()); var id = p.readVarString('utf8'); + var initialized = p.readU8() === 1; var type = p.readU8() === 0 ? 'pubkeyhash' : 'multisig'; var m = p.readU8(); var n = p.readU8(); @@ -1625,6 +1717,7 @@ Wallet.parseRaw = function parseRaw(data) { return { network: network.type, id: id, + initialized: initialized, type: type, m: m, n: n, @@ -1638,6 +1731,16 @@ Wallet.parseRaw = function parseRaw(data) { }; }; +/** + * Instantiate a wallet from serialized data. + * @param {Buffer} data + * @returns {Wallet} + */ + +Wallet.fromRaw = function fromRaw(data) { + return new Wallet(Wallet.parseRaw(data)); +}; + /** * Instantiate a Wallet from a * jsonified wallet object. @@ -1669,7 +1772,7 @@ function MasterKey(options) { if (!(this instanceof MasterKey)) return new MasterKey(options); - this.encrypted = options.encrypted; + this.encrypted = !!options.encrypted; this.xprivkey = options.xprivkey; this.phrase = options.phrase; this.passphrase = options.passphrase; @@ -1696,6 +1799,8 @@ MasterKey.prototype.encrypt = function encrypt(passphrase) { if (this.encrypted) return; + assert(passphrase, 'Passphrase is required.'); + this.key = null; this.encrypted = true; this.xprivkey = utils.encrypt(this.xprivkey, passphrase); @@ -1812,181 +1917,12 @@ MasterKey.fromJSON = function fromJSON(json) { MasterKey.isMasterKey = function isMasterKey(obj) { return obj - && obj.xprivkey + && typeof obj.encrypted === 'boolean' && typeof obj.decrypt === 'function'; }; -/* - * CWallet - */ - -function CWallet(id, db) { - var i, key; - - if (!(this instanceof CWallet)) - return new CWallet(id, db); - - EventEmitter.call(this); - - this.network = db.network; - this.db = db; - this.id = id; - this.loaded = false; -} - -utils.inherits(CWallet, EventEmitter); - -CWallet.prototype.open = function open(callback) { - var self = this; - - if (this.loaded) - return utils.nextTick(callback); - - this.loaded = true; - - this.db.register(this.id, this); - - return utils.nextTick(callback); -}; - -CWallet.prototype.close = -CWallet.prototype.destroy = function destroy(callback) { - callback = utils.ensure(callback); - - if (!this.db) - return utils.nextTick(callback); - - this.db.unregister(this.id, this); - this.db = null; - - return utils.nextTick(callback); -}; - -CWallet.prototype.addKey = function addKey(key, callback) { - this.db.addKey(this.id, key, callback); -}; - -CWallet.prototype.removeKey = function removeKey(key, callback) { - this.db.removeKey(this.id, key, callback); -}; - -CWallet.prototype.getID = function getID() { - return this.id; -}; - -CWallet.prototype.createReceive = function createReceive(callback) { - return this.db.createAddress(this.id, false, callback); -}; - -CWallet.prototype.createChange = function createChange(callback) { - return this.db.createAddress(this.id, true, callback); -}; - -CWallet.prototype.createAddress = function createAddress(change, callback) { - return this.db.createAddress(this.id, change, callback); -}; - -CWallet.prototype.hasAddress = function hasAddress(address, callback) { - this.db.hasAddress(this.id, address, callback); -}; - -CWallet.prototype.fill = function fill(tx, options, callback) { - this.db.fill(this.id, tx, options, callback); -}; - -CWallet.prototype.fillCoins = function fillCoins(tx, callback) { - this.db.fillHistory(this.id, tx, callback); -}; - -CWallet.prototype.getCoin = function getCoin(hash, index, callback) { - this.db.getCoin(hash, index, callback); -}; - -CWallet.prototype.getTX = function getTX(hash, callback) { - this.db.getTX(hash, callback); -}; - -CWallet.prototype.createTX = function createTX(options, outputs, callback) { - this.db.createTX(this.id, options, outputs, callback); -}; - -CWallet.prototype.getPath = function getPath(address, callback) { - if (!address || typeof address !== 'string') - return callback(); - this.db.getPath(this.id, address, callback); -}; - -CWallet.prototype.getRedeem = function getRedeem(hash, callback) { - this.db.getRedeem(this.id, hash, callback); -}; - -CWallet.prototype.zap = function zap(now, age, callback) { - return this.db.zap(this.id, now, age, callback); -}; - -CWallet.prototype.scriptInputs = function scriptInputs(tx, callback) { - this.db.scriptInputs(this.id, tx, callback); -}; - -CWallet.prototype.sign = function sign(tx, options, callback) { - this.db.sign(this.id, tx, options, callback); -}; - -CWallet.prototype.addTX = function addTX(tx, callback) { - return this.db.addTX(tx, callback); -}; - -CWallet.prototype.getHistory = function getHistory(callback) { - return this.db.getHistory(this.id, callback); -}; - -CWallet.prototype.getCoins = function getCoins(callback) { - return this.db.getCoins(this.id, callback); -}; - -CWallet.prototype.getUnconfirmed = function getUnconfirmed(callback) { - return this.db.getUnconfirmed(this.id, callback); -}; - -CWallet.prototype.getBalance = function getBalance(callback) { - return this.db.getBalance(this.id, callback); -}; - -CWallet.prototype.getLastTime = function getLastTime(callback) { - return this.db.getLastTime(this.id, callback); -}; - -CWallet.prototype.getLast = function getLast(limit, callback) { - return this.db.getLast(this.id, limit, callback); -}; - -CWallet.prototype.getTimeRange = function getTimeRange(options, callback) { - return this.db.getTimeRange(this.id, options, callback); -}; - -CWallet.prototype.getReceiveAddress = function getReceiveAddress(callback) { - return this.db.getReceiveAddress(this.id, callback); -}; - -CWallet.prototype.getInfo = function getInfo(callback) { - return this.db.getInfo(this.id, callback); -}; - -CWallet.prototype.inspect = function inspect() { - return ''; -}; - -CWallet.isCWallet = function isCWallet(obj) { - return obj - && obj.db - && obj.id - && obj.getInfo === 'function'; -}; - /* * Expose */ module.exports = Wallet; -module.exports.CWallet = CWallet; -bcoin.cwallet = CWallet; diff --git a/lib/bcoin/walletdb.js b/lib/bcoin/walletdb.js index 4d292bc7..a7e373a7 100644 --- a/lib/bcoin/walletdb.js +++ b/lib/bcoin/walletdb.js @@ -16,7 +16,6 @@ var bcoin = require('./env'); var EventEmitter = require('events').EventEmitter; var utils = require('./utils'); var assert = utils.assert; -var DUMMY = new Buffer([0]); var BufferReader = require('./reader'); var BufferWriter = require('./writer'); @@ -116,19 +115,6 @@ WalletDB.prototype._init = function _init() { useFilter: true }); - this.db.open(function(err) { - if (err) - return self.emit('error', err); - - self.tx._loadFilter(function(err) { - if (err) - return self.emit('error', err); - - self.emit('open'); - self.loaded = true; - }); - }); - this.tx.on('error', function(err) { self.emit('error', err); }); @@ -164,7 +150,7 @@ WalletDB.prototype._init = function _init() { utils.forEachSerial(map.output, function(id, next) { if (self.listeners('balance').length === 0 - && !self.hasListener(id, ' balance')) { + && !self.hasListener(id, 'balance')) { return next(); } @@ -186,6 +172,19 @@ WalletDB.prototype._init = function _init() { self.emit('balances', balances, map); }); }); + + this.db.open(function(err) { + if (err) + return self.emit('error', err); + + self.tx._loadFilter(function(err) { + if (err) + return self.emit('error', err); + + self.emit('open'); + self.loaded = true; + }); + }); }; WalletDB.prototype.sync = function sync(tx, map, callback) { @@ -220,173 +219,80 @@ WalletDB.prototype.destroy = function destroy(callback) { }; /** - * Helper function to get a wallet. - * @private + * Register a wallet with the walletdb. * @param {WalletID} id - * @param {Function} errback - * @param {Function} callback + * @param {Wallet} wallet */ -WalletDB.prototype.fetchWallet = function fetchWallet(id, errback, callback) { - var self = this; +WalletDB.prototype.register = function register(wallet) { + var id = wallet.id; - callback = utils.ensure(callback); + if (!this.watchers[id]) + this.watchers[id] = { wallet: wallet, refs: 0 }; - this.get(id, function(err, _, wallet) { - if (err) - return errback(err); + // Should never happen, and if it does, I will cry. + assert(this.watchers[id].wallet === wallet, 'I\'m crying.'); - if (!wallet) - return errback(new Error('No wallet.')); - - callback(wallet); - }); -}; - -WalletDB.prototype.syncOutputDepth = function syncOutputDepth(id, tx, callback) { - this.fetchWallet(id, callback, function(wallet) { - wallet.syncOutputDepth(tx, callback); - }); -}; - -WalletDB.prototype.createAddress = function createAddress(id, change, callback) { - this.fetchWallet(id, callback, function(wallet) { - wallet.createAddress(change, callback); - }); -}; - -WalletDB.prototype.getReceiveAddress = function getReceiveAddress(id, callback) { - this.fetchWallet(id, callback, function(wallet) { - callback(null, wallet.receiveAddress); - }); -}; - -WalletDB.prototype.getChangeAddress = function getChangeAddress(id, callback) { - this.fetchWallet(id, callback, function(wallet) { - callback(null, wallet.changeAddress); - }); -}; - -WalletDB.prototype.fill = function fill(id, tx, options, callback) { - this.fetchWallet(id, callback, function(wallet) { - wallet.fill(tx, options, callback); - }); -}; - -WalletDB.prototype.scriptInputs = function scriptInputs(id, tx, callback) { - this.fetchWallet(id, callback, function(wallet) { - wallet.scriptInputs(tx, callback); - }); -}; - -WalletDB.prototype.sign = function sign(id, tx, options, callback) { - if (typeof options === 'function') { - callback = options; - options = {}; - } - - if (typeof options === 'string' || Buffer.isBuffer(options)) - options = { passphrase: options }; - - this.fetchWallet(id, callback, function(wallet) { - wallet.sign(tx, options, callback); - }); -}; - -WalletDB.prototype.createTX = function createTX(id, options, outputs, callback) { - this.fetchWallet(id, callback, function(wallet) { - wallet.createTX(options, outputs, callback); - }); -}; - -WalletDB.prototype.addKey = function addKey(id, key, callback) { - this.fetchWallet(id, callback, function(wallet) { - wallet.addKey(key, callback); - }); -}; - -WalletDB.prototype.removeKey = function removeKey(id, key, callback) { - this.fetchWallet(id, callback, function(wallet) { - wallet.removeKey(key, callback); - }); -}; - -WalletDB.prototype.getInfo = function getInfo(id, callback) { - this.fetchWallet(id, callback, function(wallet) { - callback(null, wallet); - }); -}; - -WalletDB.prototype.getRedeem = function getRedeem(id, hash, callback) { - this.fetchWallet(id, callback, function(wallet) { - wallet.getRedeem(hash, callback); - }); + // We do some reference counting here + // because we're thug like that (police + // have a fit when your papers legit). + this.watchers[id].refs++; }; /** - * Save a "naked" (non-instantiated) wallet. Will - * also index the address table. - * @param {WalletID} - * @param {Object} json - "Naked" wallet. - * @param {Function} callback - Returns [Error, Object]. + * Unregister a wallet with the walletdb. + * @param {WalletID} id + * @param {Wallet} wallet */ -WalletDB.prototype.saveJSON = function saveJSON(id, json, callback) { - this.db.put('w/' + id, json, callback); +WalletDB.prototype.unregister = function unregister(wallet) { + var id = wallet.id; + var watcher = this.watchers[id]; + var i; + + if (!watcher) + return; + + assert(watcher.wallet === wallet); + assert(watcher.refs !== 0, '`wallet.destroy()` called twice!'); + + if (--watcher.refs === 0) + delete this.watchers[id]; }; /** - * Remove wallet from the database. + * Fire an event for a registered wallet. * @param {WalletID} id - * @param {Function} callback - Returns [Error, Object]. + * @param {...Object} args */ -WalletDB.prototype.removeJSON = function removeJSON(id, callback) { - var self = this; +WalletDB.prototype.fire = function fire(id) { + var args = Array.prototype.slice.call(arguments, 1); + var watcher = this.watchers[id]; - callback = utils.ensure(callback); + if (!watcher) + return; - this.getJSON(id, function(err, json) { - if (err) - return callback(err); - - self.db.del('w/' + id, function(err) { - if (err && err.type !== 'NotFoundError') - return callback(err); - - return callback(null, json); - }); - }); + watcher.wallet.emit.apply(watcher.wallet, args); }; /** - * Retrieve object from the database. - * @private + * Test for a listener on a registered wallet. * @param {WalletID} id - * @param {Function} callback - Returns [Error, Object(nakedWallet)]. + * @param {String} event + * @returns {Boolean} */ -WalletDB.prototype.getJSON = function getJSON(id, callback) { - callback = utils.ensure(callback); +WalletDB.prototype.hasListener = function hasListener(id, event) { + var watcher = this.watchers[id]; - if (!id) - return callback(); + if (!watcher) + return false; - this.db.get('w/' + id, function(err, json) { - if (err && err.type === 'NotFoundError') - return callback(); + if (watcher.wallet.listeners(event).length !== 0) + return true; - if (err) - return callback(err); - - try { - json = bcoin.wallet.parseRaw(json); - } catch (e) { - return callback(e); - } - - return callback(null, json); - }); + return false; }; /** @@ -397,30 +303,39 @@ WalletDB.prototype.getJSON = function getJSON(id, callback) { WalletDB.prototype.get = function get(id, callback) { var self = this; + var wallet; - callback = utils.ensure(callback); + if (!id) + return callback(); - return this.getJSON(id, function(err, json) { - var wallet; + if (this.watchers[id]) { + this.watchers[id].refs++; + return callback(null, this.watchers[id].wallet); + } + + this.db.get('w/' + id, function(err, data) { + if (err && err.type === 'NotFoundError') + return callback(); if (err) return callback(err); - if (!json) + if (!data) return callback(); try { - json.db = self; - wallet = new bcoin.wallet(json); + data = bcoin.wallet.parseRaw(data); + data.db = self; + wallet = new bcoin.wallet(data); } catch (e) { return callback(e); } - wallet.init(function(err) { + wallet.open(function(err) { if (err) return callback(err); - return callback(null, new bcoin.cwallet(wallet.id, self), wallet); + return callback(null, wallet); }); }); }; @@ -432,13 +347,7 @@ WalletDB.prototype.get = function get(id, callback) { */ WalletDB.prototype.save = function save(wallet, callback) { - var self = this; - if (Array.isArray(wallet)) { - return utils.forEachSerial(wallet, function(wallet, next) { - self.save(wallet, next); - }, callback); - } - this.saveJSON(wallet.id, wallet.toRaw(), callback); + this.db.put('w/' + wallet.id, wallet.toRaw(), callback); }; /** @@ -448,13 +357,12 @@ WalletDB.prototype.save = function save(wallet, callback) { */ WalletDB.prototype.remove = function remove(id, callback) { - var self = this; - if (Array.isArray(wallet)) { - return utils.forEachSerial(id, function(id, next) { - self.remove(id, next); - }, callback); - } - return this.removeJSON(id, callback); + this.db.del('w/' + id, function(err) { + if (err && err.type !== 'NotFoundError') + return callback(err); + + return callback(); + }); }; /** @@ -477,6 +385,8 @@ WalletDB.prototype.create = function create(options, callback) { if (exists) return callback(new Error('Wallet already exists.')); + options = utils.merge({}, options); + if (self.network.witness) options.witness = options.witness !== false; @@ -484,11 +394,11 @@ WalletDB.prototype.create = function create(options, callback) { options.db = self; wallet = new bcoin.wallet(options); - wallet.init(function(err) { + wallet.open(function(err) { if (err) return callback(err); - return callback(null, new bcoin.cwallet(wallet.id, self), wallet); + return callback(null, wallet); }); }); }; @@ -515,12 +425,12 @@ WalletDB.prototype.has = function has(id, callback) { WalletDB.prototype.ensure = function ensure(options, callback) { var self = this; - return this.get(options.id, function(err, cwallet, wallet) { + return this.get(options.id, function(err, wallet) { if (err) return callback(err); - if (cwallet) - return callback(null, cwallet, wallet); + if (wallet) + return callback(null, wallet); self.create(options, callback); }); @@ -611,6 +521,9 @@ WalletDB.prototype.hasAddress = function hasAddress(id, address, callback) { */ WalletDB.prototype.getAddress = function getAddress(address, callback) { + if (!address) + return callback(); + this.db.fetch('W/' + address, parsePaths, callback); }; @@ -778,78 +691,115 @@ WalletDB.prototype.removeBlock = function removeBlock(block, callback) { }; /** - * Register an event emitter with the walletdb. + * Helper function to get a wallet. + * @private * @param {WalletID} id - * @param {EventEmitter} watcher + * @param {Function} callback + * @param {Function} handler */ -WalletDB.prototype.register = function register(id, watcher) { - if (!this.watchers[id]) - this.watchers[id] = []; +WalletDB.prototype.fetchWallet = function fetchWallet(id, callback, handler) { + var self = this; - if (this.watchers[id].indexOf(watcher) === -1) - this.watchers[id].push(watcher); + this.get(id, function(err, wallet) { + if (err) + return callback(err); + + if (!wallet) + return callback(new Error('No wallet.')); + + handler(wallet, function(err, result) { + // Kill the reference. + self.unregister(wallet); + + if (err) + return callback(err); + + callback(null, result); + }); + }); }; -/** - * Unregister an event emitter with the walletdb. - * @param {WalletID} id - * @param {EventEmitter} watcher - */ - -WalletDB.prototype.unregister = function unregister(id, watcher) { - var watchers = this.watchers[id]; - var i; - - if (!watchers) - return; - - i = watchers.indexOf(watcher); - if (i !== -1) - watchers.splice(i, 1); - - if (watchers.length === 0) - delete this.watchers[id]; +WalletDB.prototype.syncOutputDepth = function syncOutputDepth(id, tx, callback) { + this.fetchWallet(id, callback, function(wallet, callback) { + wallet.syncOutputDepth(tx, callback); + }); }; -/** - * Fire an event for all registered event emitters. - * @param {WalletID} id - * @param {...Object} args - */ - -WalletDB.prototype.fire = function fire(id) { - var args = Array.prototype.slice.call(arguments, 1); - var watchers = this.watchers[id]; - var i; - - if (!watchers) - return; - - for (i = 0; i < watchers.length; i++) - watchers[i].emit.apply(watchers[i], args); +WalletDB.prototype.createAddress = function createAddress(id, change, callback) { + this.fetchWallet(id, callback, function(wallet, callback) { + wallet.createAddress(change, callback); + }); }; -/** - * Test for a listener on a registered event emitter. - * @param {WalletID} id - * @param {String} event - * @returns {Boolean} - */ +WalletDB.prototype.getReceiveAddress = function getReceiveAddress(id, callback) { + this.fetchWallet(id, callback, function(wallet, callback) { + callback(null, wallet.receiveAddress); + }); +}; -WalletDB.prototype.hasListener = function hasListener(id, event) { - var watchers = this.watchers[id]; - var i; +WalletDB.prototype.getChangeAddress = function getChangeAddress(id, callback) { + this.fetchWallet(id, callback, function(wallet, callback) { + callback(null, wallet.changeAddress); + }); +}; - if (!watchers) - return false; +WalletDB.prototype.fill = function fill(id, tx, options, callback) { + this.fetchWallet(id, callback, function(wallet, callback) { + wallet.fill(tx, options, callback); + }); +}; - for (i = 0; i < watchers.length; i++) { - if (watchers[i].listeners(event).length !== 0) - return true; +WalletDB.prototype.scriptInputs = function scriptInputs(id, tx, callback) { + this.fetchWallet(id, callback, function(wallet, callback) { + wallet.scriptInputs(tx, callback); + }); +}; + +WalletDB.prototype.sign = function sign(id, tx, options, callback) { + if (typeof options === 'function') { + callback = options; + options = {}; } - return false; + this.fetchWallet(id, callback, function(wallet, callback) { + wallet.sign(tx, options, callback); + }); +}; + +WalletDB.prototype.createTX = function createTX(id, options, outputs, callback) { + if (typeof outputs === 'function') { + callback = outputs; + outputs = null; + } + + this.fetchWallet(id, callback, function(wallet, callback) { + wallet.createTX(options, outputs, callback); + }); +}; + +WalletDB.prototype.addKey = function addKey(id, key, callback) { + this.fetchWallet(id, callback, function(wallet, callback) { + wallet.addKey(key, callback); + }); +}; + +WalletDB.prototype.removeKey = function removeKey(id, key, callback) { + this.fetchWallet(id, callback, function(wallet, callback) { + wallet.removeKey(key, callback); + }); +}; + +WalletDB.prototype.getInfo = function getInfo(id, callback) { + this.fetchWallet(id, callback, function(wallet, callback) { + callback(null, wallet); + }); +}; + +WalletDB.prototype.getRedeem = function getRedeem(id, hash, callback) { + this.fetchWallet(id, callback, function(wallet, callback) { + wallet.getRedeem(hash, callback); + }); }; /* diff --git a/test/chain-test.js b/test/chain-test.js index 81f0e50b..84ada32a 100644 --- a/test/chain-test.js +++ b/test/chain-test.js @@ -17,8 +17,7 @@ describe('Chain', function() { walletdb = new bcoin.walletdb({ name: 'chain-test-wdb', db: 'memory' }); wallet = new bcoin.wallet({ db: walletdb }); miner = new bcoin.miner({ - chain: chain, - address: wallet.getAddress() + chain: chain }); chain.on('error', function() {}); @@ -73,8 +72,12 @@ describe('Chain', function() { it('should open chain and miner', function(cb) { miner.open(function(err) { - assert.ifError(cb); - wallet.init(cb); + assert.ifError(err); + wallet.open(function(err) { + assert.ifError(err); + miner.address = wallet.getAddress(); + cb(); + }); }); }); diff --git a/test/mempool-test.js b/test/mempool-test.js index 335b35de..eaa8170d 100644 --- a/test/mempool-test.js +++ b/test/mempool-test.js @@ -19,18 +19,24 @@ describe('Mempool', function() { db: 'memory' }); + var w; + mempool.on('error', function() {}); it('should open mempool', function(cb) { mempool.open(cb); }); - it('should handle incoming orphans and TXs', function(cb) { - var w = new bcoin.wallet(); + it('should open wallet', function(cb) { + w = new bcoin.wallet(); + w.open(cb); + }); + it('should handle incoming orphans and TXs', function(cb) { + var kp = bcoin.hd.generate(); // Coinbase var t1 = bcoin.mtx().addOutput(w, 50000).addOutput(w, 10000); // 10000 instead of 1000 - var prev = new bcoin.script([w.publicKey, opcodes.OP_CHECKSIG]); + var prev = new bcoin.script([kp.publicKey, opcodes.OP_CHECKSIG]); var dummyInput = { prevout: { hash: constants.ONE_HASH.toString('hex'), @@ -49,84 +55,96 @@ describe('Mempool', function() { sequence: 0xffffffff }; t1.addInput(dummyInput); - t1.inputs[0].script = new bcoin.script([t1.createSignature(0, prev, w.privateKey, 'all', 0)]), + t1.inputs[0].script = new bcoin.script([t1.createSignature(0, prev, kp.privateKey, 'all', 0)]), // balance: 51000 - w.sign(t1); - var t2 = bcoin.mtx().addInput(t1, 0) // 50000 - .addOutput(w, 20000) - .addOutput(w, 20000); - // balance: 49000 - w.sign(t2); - var t3 = bcoin.mtx().addInput(t1, 1) // 10000 - .addInput(t2, 0) // 20000 - .addOutput(w, 23000); - // balance: 47000 - w.sign(t3); - var t4 = bcoin.mtx().addInput(t2, 1) // 24000 - .addInput(t3, 0) // 23000 - .addOutput(w, 11000) - .addOutput(w, 11000); - // balance: 22000 - w.sign(t4); - var f1 = bcoin.mtx().addInput(t4, 1) // 11000 - .addOutput(new bcoin.wallet(), 9000); - // balance: 11000 - w.sign(f1); - var fake = bcoin.mtx().addInput(t1, 1) // 1000 (already redeemed) - .addOutput(w, 6000); // 6000 instead of 500 - // Script inputs but do not sign - w.scriptInputs(fake); - // Fake signature - fake.inputs[0].script.code[0] = new Buffer([0,0,0,0,0,0,0,0,0]); - // balance: 11000 - [t2, t3, t4, f1, fake].forEach(function(tx) { - tx.inputs.forEach(function(input) { - delete input.coin; - }); - }); - - // Just for debugging - t1.hint = 't1'; - t2.hint = 't2'; - t3.hint = 't3'; - t4.hint = 't4'; - f1.hint = 'f1'; - fake.hint = 'fake'; - - mempool.addTX(fake, function(err) { + w.sign(t1, function(err, total) { assert.ifError(err); - mempool.addTX(t4, function(err) { + var t2 = bcoin.mtx().addInput(t1, 0) // 50000 + .addOutput(w, 20000) + .addOutput(w, 20000); + // balance: 49000 + w.sign(t2, function(err, total) { assert.ifError(err); - mempool.getBalance(function(err, balance) { + var t3 = bcoin.mtx().addInput(t1, 1) // 10000 + .addInput(t2, 0) // 20000 + .addOutput(w, 23000); + // balance: 47000 + w.sign(t3, function(err, total) { assert.ifError(err); - assert.equal(balance.total, 0); - mempool.addTX(t1, function(err) { + var t4 = bcoin.mtx().addInput(t2, 1) // 24000 + .addInput(t3, 0) // 23000 + .addOutput(w, 11000) + .addOutput(w, 11000); + // balance: 22000 + w.sign(t4, function(err, total) { assert.ifError(err); - mempool.getBalance(function(err, balance) { + var f1 = bcoin.mtx().addInput(t4, 1) // 11000 + .addOutput(bcoin.address.fromData(new Buffer([])).toBase58(), 9000); + // balance: 11000 + w.sign(f1, function(err, total) { assert.ifError(err); - assert.equal(balance.total, 60000); - mempool.addTX(t2, function(err) { + var fake = bcoin.mtx().addInput(t1, 1) // 1000 (already redeemed) + .addOutput(w, 6000); // 6000 instead of 500 + // Script inputs but do not sign + w.scriptInputs(fake, function(err) { assert.ifError(err); - mempool.getBalance(function(err, balance) { + // Fake signature + fake.inputs[0].script.code[0] = new Buffer([0,0,0,0,0,0,0,0,0]); + // balance: 11000 + [t2, t3, t4, f1, fake].forEach(function(tx) { + tx.inputs.forEach(function(input) { + delete input.coin; + }); + }); + + // Just for debugging + t1.hint = 't1'; + t2.hint = 't2'; + t3.hint = 't3'; + t4.hint = 't4'; + f1.hint = 'f1'; + fake.hint = 'fake'; + + mempool.addTX(fake, function(err) { assert.ifError(err); - assert.equal(balance.total, 50000); - mempool.addTX(t3, function(err) { + mempool.addTX(t4, function(err) { assert.ifError(err); mempool.getBalance(function(err, balance) { assert.ifError(err); - assert.equal(balance.total, 22000); - mempool.addTX(f1, function(err) { + assert.equal(balance.total, 0); + mempool.addTX(t1, function(err) { assert.ifError(err); mempool.getBalance(function(err, balance) { assert.ifError(err); - assert.equal(balance.total, 20000); - mempool.getHistory(function(err, txs) { - assert(txs.some(function(tx) { - return tx.hash('hex') === f1.hash('hex'); - })); + assert.equal(balance.total, 60000); + mempool.addTX(t2, function(err) { + assert.ifError(err); + mempool.getBalance(function(err, balance) { + assert.ifError(err); + assert.equal(balance.total, 50000); + mempool.addTX(t3, function(err) { + assert.ifError(err); + mempool.getBalance(function(err, balance) { + assert.ifError(err); + assert.equal(balance.total, 22000); + mempool.addTX(f1, function(err) { + assert.ifError(err); + mempool.getBalance(function(err, balance) { + assert.ifError(err); + assert.equal(balance.total, 20000); + mempool.getHistory(function(err, txs) { + assert(txs.some(function(tx) { + return tx.hash('hex') === f1.hash('hex'); + })); - cb(); + cb(); + }); + }); + }); + }); + }); + }); }); }); }); @@ -142,11 +160,10 @@ describe('Mempool', function() { }); it('should handle locktime', function(cb) { - var w = new bcoin.wallet(); - + var kp = bcoin.hd.generate(); // Coinbase var t1 = bcoin.mtx().addOutput(w, 50000).addOutput(w, 10000); // 10000 instead of 1000 - var prev = new bcoin.script([w.publicKey, opcodes.OP_CHECKSIG]); + var prev = new bcoin.script([kp.publicKey, opcodes.OP_CHECKSIG]); var prevHash = bcoin.ec.random(32).toString('hex'); var dummyInput = { prevout: { @@ -168,7 +185,7 @@ describe('Mempool', function() { t1.addInput(dummyInput); t1.setLocktime(200); chain.tip.height = 200; - t1.inputs[0].script = new bcoin.script([t1.createSignature(0, prev, w.privateKey, 'all', 0)]), + t1.inputs[0].script = new bcoin.script([t1.createSignature(0, prev, kp.privateKey, 'all', 0)]), mempool.addTX(t1, function(err) { chain.tip.height = 0; assert.ifError(err); @@ -177,11 +194,10 @@ describe('Mempool', function() { }); it('should handle invalid locktime', function(cb) { - var w = new bcoin.wallet(); - + var kp = bcoin.hd.generate(); // Coinbase var t1 = bcoin.mtx().addOutput(w, 50000).addOutput(w, 10000); // 10000 instead of 1000 - var prev = new bcoin.script([w.publicKey, opcodes.OP_CHECKSIG]); + var prev = new bcoin.script([kp.publicKey, opcodes.OP_CHECKSIG]); var prevHash = bcoin.ec.random(32).toString('hex'); var dummyInput = { prevout: { @@ -203,7 +219,7 @@ describe('Mempool', function() { t1.addInput(dummyInput); t1.setLocktime(200); chain.tip.height = 200 - 1; - t1.inputs[0].script = new bcoin.script([t1.createSignature(0, prev, w.privateKey, 'all', 0)]), + t1.inputs[0].script = new bcoin.script([t1.createSignature(0, prev, kp.privateKey, 'all', 0)]), mempool.addTX(t1, function(err) { chain.tip.height = 0; assert(err); diff --git a/test/wallet-test.js b/test/wallet-test.js index 6e169c07..80c52da1 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -82,40 +82,37 @@ describe('Wallet', function() { if (witness) flags |= bcoin.protocol.constants.flags.VERIFY_WITNESS; - wdb.create({ witness: witness }, function(err, w, w2) { + wdb.create({ witness: witness }, function(err, w) { assert.ifError(err); - w.getReceiveAddress(function(err, a) { - if (witness) - assert(bcoin.address.parseBase58(a.getAddress()).type === 'witnesspubkeyhash'); - else - assert(bcoin.address.parseBase58(a.getAddress()).type === 'pubkeyhash'); + if (witness) + assert(bcoin.address.parseBase58(w.getAddress()).type === 'witnesspubkeyhash'); + else + assert(bcoin.address.parseBase58(w.getAddress()).type === 'pubkeyhash'); - // Input transcation - var src = bcoin.mtx({ - outputs: [{ - value: 5460 * 2, - address: bullshitNesting - ? a.getProgramAddress() - : a.getAddress() - }, { - value: 5460 * 2, - address: bcoin.address.fromData(new Buffer([])).toBase58() - }] - }); + // Input transcation + var src = bcoin.mtx({ + outputs: [{ + value: 5460 * 2, + address: bullshitNesting + ? w.getProgramAddress() + : w.getAddress() + }, { + value: 5460 * 2, + address: bcoin.address.fromData(new Buffer([])).toBase58() + }] + }); - src.addInput(dummyInput); + src.addInput(dummyInput); - var tx = bcoin.mtx() - .addInput(src, 0) - .addOutput(a.getAddress(), 5460); + var tx = bcoin.mtx() + .addInput(src, 0) + .addOutput(w.getAddress(), 5460); - w.sign(tx, function(err) { - assert.ifError(err); - utils.print(tx); - assert(tx.verify(null, true, flags)); - cb(); - }); + w.sign(tx, function(err) { + assert.ifError(err); + assert(tx.verify(null, true, flags)); + cb(); }); }); } @@ -174,107 +171,109 @@ describe('Wallet', function() { var dw, di; it('should have TX pool and be serializable', function(cb) { wdb.create({}, function(err, w) { - assert.ifError(err); - w.getReceiveAddress(function(err, aw) { assert.ifError(err); wdb.create({}, function(err, f) { - assert.ifError(err); - f.getReceiveAddress(function(err, af) { assert.ifError(err); dw = w; // Coinbase - var t1 = bcoin.mtx().addOutput(aw, 50000).addOutput(aw, 1000); + var t1 = bcoin.mtx().addOutput(w, 50000).addOutput(w, 1000); t1.addInput(dummyInput); // balance: 51000 // w.sign(t1); w.sign(t1, function(err) { - assert.ifError(err); - var t2 = bcoin.mtx().addInput(t1, 0) // 50000 - .addOutput(aw, 24000) - .addOutput(aw, 24000); - di = t2.inputs[0]; - // balance: 49000 - // w.sign(t2); - w.sign(t2, function(err) { - assert.ifError(err); - var t3 = bcoin.mtx().addInput(t1, 1) // 1000 - .addInput(t2, 0) // 24000 - .addOutput(aw, 23000); - // balance: 47000 - // w.sign(t3); - w.sign(t3, function(err) { - assert.ifError(err); - var t4 = bcoin.mtx().addInput(t2, 1) // 24000 - .addInput(t3, 0) // 23000 - .addOutput(aw, 11000) - .addOutput(aw, 11000); - // balance: 22000 - // w.sign(t4); - w.sign(t4, function(err) { - assert.ifError(err); - var f1 = bcoin.mtx().addInput(t4, 1) // 11000 - .addOutput(af, 10000); - // balance: 11000 - // w.sign(f1); - w.sign(f1, function(err) { - assert.ifError(err); - var fake = bcoin.mtx().addInput(t1, 1) // 1000 (already redeemed) - .addOutput(aw, 500); - // Script inputs but do not sign - // w.scriptInputs(fake); - w.scriptInputs(fake, function(err) { - assert.ifError(err); - // Fake signature - fake.inputs[0].script.code[0] = new Buffer([0,0,0,0,0,0,0,0,0]); - // balance: 11000 - - // Just for debugging - t1.hint = 't1'; - t2.hint = 't2'; - t3.hint = 't3'; - t4.hint = 't4'; - f1.hint = 'f1'; - fake.hint = 'fake'; - - // Fake TX should temporarly change output - wdb.addTX(fake, function(err) { assert.ifError(err); - wdb.addTX(t4, function(err) { + var t2 = bcoin.mtx().addInput(t1, 0) // 50000 + .addOutput(w, 24000) + .addOutput(w, 24000); + di = t2.inputs[0]; + // balance: 49000 + // w.sign(t2); + w.sign(t2, function(err) { assert.ifError(err); - w.getBalance(function(err, balance) { + var t3 = bcoin.mtx().addInput(t1, 1) // 1000 + .addInput(t2, 0) // 24000 + .addOutput(w, 23000); + // balance: 47000 + // w.sign(t3); + w.sign(t3, function(err) { assert.ifError(err); - assert.equal(balance.total, 22500); - wdb.addTX(t1, function(err) { - w.getBalance(function(err, balance) { + var t4 = bcoin.mtx().addInput(t2, 1) // 24000 + .addInput(t3, 0) // 23000 + .addOutput(w, 11000) + .addOutput(w, 11000); + // balance: 22000 + // w.sign(t4); + w.sign(t4, function(err) { + assert.ifError(err); + var f1 = bcoin.mtx().addInput(t4, 1) // 11000 + .addOutput(f, 10000); + // balance: 11000 + // w.sign(f1); + w.sign(f1, function(err) { assert.ifError(err); - assert.equal(balance.total, 73000); - wdb.addTX(t2, function(err) { + var fake = bcoin.mtx().addInput(t1, 1) // 1000 (already redeemed) + .addOutput(w, 500); + // Script inputs but do not sign + // w.scriptInputs(fake); + w.scriptInputs(fake, function(err) { assert.ifError(err); - w.getBalance(function(err, balance) { + // Fake signature + fake.inputs[0].script.code[0] = new Buffer([0,0,0,0,0,0,0,0,0]); + // balance: 11000 + + // Just for debugging + t1.hint = 't1'; + t2.hint = 't2'; + t3.hint = 't3'; + t4.hint = 't4'; + f1.hint = 'f1'; + fake.hint = 'fake'; + + // Fake TX should temporarly change output + wdb.addTX(fake, function(err) { assert.ifError(err); - assert.equal(balance.total, 47000); - wdb.addTX(t3, function(err) { + wdb.addTX(t4, function(err) { assert.ifError(err); w.getBalance(function(err, balance) { assert.ifError(err); - assert.equal(balance.total, 22000); - wdb.addTX(f1, function(err) { - assert.ifError(err); + assert.equal(balance.total, 22500); + wdb.addTX(t1, function(err) { w.getBalance(function(err, balance) { assert.ifError(err); - assert.equal(balance.total, 11000); - w.getHistory(function(err, txs) { - assert(txs.some(function(tx) { - return tx.hash('hex') === f1.hash('hex'); - })); + assert.equal(balance.total, 73000); + wdb.addTX(t2, function(err) { + assert.ifError(err); + w.getBalance(function(err, balance) { + assert.ifError(err); + assert.equal(balance.total, 47000); + wdb.addTX(t3, function(err) { + assert.ifError(err); + w.getBalance(function(err, balance) { + assert.ifError(err); + assert.equal(balance.total, 22000); + wdb.addTX(f1, function(err) { + assert.ifError(err); + w.getBalance(function(err, balance) { + assert.ifError(err); + assert.equal(balance.total, 11000); + w.getHistory(function(err, txs) { + assert(txs.some(function(tx) { + return tx.hash('hex') === f1.hash('hex'); + })); - //var w2 = bcoin.wallet.fromJSON(w.toJSON()); - // assert.equal(w2.getBalance(), 11000); - // assert(w2.getHistory().some(function(tx) { - // return tx.hash('hex') === f1.hash('hex'); - // })); - cb(); + //var w2 = bcoin.wallet.fromJSON(w.toJSON()); + // assert.equal(w2.getBalance(), 11000); + // assert(w2.getHistory().some(function(tx) { + // return tx.hash('hex') === f1.hash('hex'); + // })); + cb(); + }); + }); + }); + }); + }); + }); }); }); }); @@ -285,17 +284,9 @@ describe('Wallet', function() { }); }); }); - }); - }); }); }); }); - }); - }); - }); - }); - }); - }); }); }); @@ -314,20 +305,16 @@ describe('Wallet', function() { it('should fill tx with inputs', function(cb) { wdb.create({}, function(err, w1) { - assert.ifError(err); - w1.getReceiveAddress(function(err, aw1) { assert.ifError(err); wdb.create({}, function(err, w2) { assert.ifError(err); - w2.getReceiveAddress(function(err, aw2) { - assert.ifError(err); // Coinbase var t1 = bcoin.mtx() - .addOutput(aw1, 5460) - .addOutput(aw1, 5460) - .addOutput(aw1, 5460) - .addOutput(aw1, 5460); + .addOutput(w1, 5460) + .addOutput(w1, 5460) + .addOutput(w1, 5460) + .addOutput(w1, 5460); t1.addInput(dummyInput); @@ -336,7 +323,7 @@ describe('Wallet', function() { assert.ifError(err); // Create new transaction - var t2 = bcoin.mtx().addOutput(aw2, 5460); + var t2 = bcoin.mtx().addOutput(w2, 5460); w1.fill(t2, { rate: 10000, round: true }, function(err) { assert.ifError(err); w1.sign(t2, function(err) { @@ -351,7 +338,7 @@ describe('Wallet', function() { assert.equal(t2.getFee(), 10920); // Create new transaction - var t3 = bcoin.mtx().addOutput(aw2, 15000); + var t3 = bcoin.mtx().addOutput(w2, 15000); w1.fill(t3, { rate: 10000, round: true }, function(err) { assert(err); assert.equal(err.requiredFunds, 25000); @@ -360,28 +347,22 @@ describe('Wallet', function() { }); }); }); - }); - }); }); }); }); it('should fill tx with inputs with accurate fee', function(cb) { wdb.create({ master: KEY1 }, function(err, w1) { - assert.ifError(err); - w1.getReceiveAddress(function(err, aw1) { assert.ifError(err); wdb.create({ master: KEY2 }, function(err, w2) { assert.ifError(err); - w2.getReceiveAddress(function(err, aw2) { - assert.ifError(err); // Coinbase var t1 = bcoin.mtx() - .addOutput(aw1, 5460) - .addOutput(aw1, 5460) - .addOutput(aw1, 5460) - .addOutput(aw1, 5460); + .addOutput(w1, 5460) + .addOutput(w1, 5460) + .addOutput(w1, 5460) + .addOutput(w1, 5460); t1.addInput(dummyInput); @@ -390,73 +371,71 @@ describe('Wallet', function() { assert.ifError(err); // Create new transaction - var t2 = bcoin.mtx().addOutput(aw2, 5460); + var t2 = bcoin.mtx().addOutput(w2, 5460); w1.fill(t2, { rate: 10000 }, function(err) { assert.ifError(err); w1.sign(t2, function(err) { - assert.ifError(err); - assert(t2.verify()); - - assert.equal(t2.getInputValue(), 16380); - - // Should now have a change output: - assert.equal(t2.getOutputValue(), 11130); - - assert.equal(t2.getFee(), 5250); - - assert.equal(t2.getCost(), 2084); - assert.equal(t2.getBaseSize(), 521); - assert.equal(t2.getSize(), 521); - assert.equal(t2.getVirtualSize(), 521); - - // Create new transaction - wdb.addTX(t2, function(err) { assert.ifError(err); - var t3 = bcoin.mtx().addOutput(aw2, 15000); - w1.fill(t3, { rate: 10000 }, function(err) { - assert(err); - cb(); + assert(t2.verify()); + + assert.equal(t2.getInputValue(), 16380); + + // Should now have a change output: + assert.equal(t2.getOutputValue(), 11130); + + assert.equal(t2.getFee(), 5250); + + assert.equal(t2.getCost(), 2084); + assert.equal(t2.getBaseSize(), 521); + assert.equal(t2.getSize(), 521); + assert.equal(t2.getVirtualSize(), 521); + + var balance; + w2.once('balance', function(b) { + balance = b; + }); + + // Create new transaction + wdb.addTX(t2, function(err) { + assert.ifError(err); + var t3 = bcoin.mtx().addOutput(w2, 15000); + w1.fill(t3, { rate: 10000 }, function(err) { + assert(err); + assert(balance.total === 5460); + cb(); + }); }); - }); }); }); }); }); }); - }); - }); }); it('should sign multiple inputs using different keys', function(cb) { wdb.create({}, function(err, w1) { - assert.ifError(err); - w1.getReceiveAddress(function(err, aw1) { assert.ifError(err); wdb.create({}, function(err, w2) { - assert.ifError(err); - w2.getReceiveAddress(function(err, aw2) { assert.ifError(err); wdb.create({}, function(err, to) { assert.ifError(err); - to.getReceiveAddress(function(err, ato) { - assert.ifError(err); // Coinbase var t1 = bcoin.mtx() - .addOutput(aw1, 5460) - .addOutput(aw1, 5460) - .addOutput(aw1, 5460) - .addOutput(aw1, 5460); + .addOutput(w1, 5460) + .addOutput(w1, 5460) + .addOutput(w1, 5460) + .addOutput(w1, 5460); t1.addInput(dummyInput); // Fake TX should temporarly change output // Coinbase var t2 = bcoin.mtx() - .addOutput(aw2, 5460) - .addOutput(aw2, 5460) - .addOutput(aw2, 5460) - .addOutput(aw2, 5460); + .addOutput(w2, 5460) + .addOutput(w2, 5460) + .addOutput(w2, 5460) + .addOutput(w2, 5460); t2.addInput(dummyInput); // Fake TX should temporarly change output @@ -468,7 +447,7 @@ describe('Wallet', function() { // Create our tx with an output var tx = bcoin.mtx(); - tx.addOutput(ato, 5460); + tx.addOutput(to, 5460); var cost = tx.getOutputValue(); var total = cost * constants.tx.MIN_FEE; @@ -479,7 +458,7 @@ describe('Wallet', function() { assert.ifError(err); // Add dummy output (for `left`) to calculate maximum TX size - tx.addOutput(aw1, 0); + tx.addOutput(w1, 0); // Add our unspent inputs to sign tx.addInput(coins1[0]); @@ -534,9 +513,6 @@ describe('Wallet', function() { }); }); }); - }); - }); - }); }); function multisig(witness, bullshitNesting, cb) { @@ -556,43 +532,6 @@ describe('Wallet', function() { var w1, w2, w3, receive; - function getInfo(callback) { - var info = { change: {} }; - utils.serial([ - function(next) { - w1.getInfo(function(err, info_) { - assert.ifError(err); - info.w1 = info_; - next(); - }); - }, - function(next) { - w2.getInfo(function(err, info_) { - assert.ifError(err); - info.w2 = info_; - next(); - }); - }, - function(next) { - w3.getInfo(function(err, info_) { - assert.ifError(err); - info.w3 = info_; - next(); - }); - }, - function(next) { - receive.getInfo(function(err, info_) { - assert.ifError(err); - info.receive = info_; - next(); - }); - } - ], function(err) { - assert.ifError(err); - return callback(null, info); - }); - } - utils.serial([ function(next) { wdb.create(utils.merge({}, options), function(err, w1_) { @@ -625,38 +564,34 @@ describe('Wallet', function() { ], function(err) { assert.ifError(err); - getInfo(function(err, a) { - assert.ifError(err); utils.serial([ - w1.addKey.bind(w1, a.w2.accountKey), - w1.addKey.bind(w1, a.w3.accountKey), - w2.addKey.bind(w2, a.w1.accountKey), - w2.addKey.bind(w2, a.w3.accountKey), - w3.addKey.bind(w3, a.w1.accountKey), - w3.addKey.bind(w3, a.w2.accountKey) + w1.addKey.bind(w1, w2.accountKey), + w1.addKey.bind(w1, w3.accountKey), + w2.addKey.bind(w2, w1.accountKey), + w2.addKey.bind(w2, w3.accountKey), + w3.addKey.bind(w3, w1.accountKey), + w3.addKey.bind(w3, w2.accountKey) ], function(err) { assert.ifError(err); - getInfo(function(err, a) { - assert.ifError(err); // w3 = bcoin.wallet.fromJSON(w3.toJSON()); // Our p2sh address - var addr = a.w1.getAddress(); + var addr = w1.getAddress(); if (witness) assert(bcoin.address.parseBase58(addr).type === 'witnessscripthash'); else assert(bcoin.address.parseBase58(addr).type === 'scripthash'); - assert.equal(a.w1.getAddress(), addr); - assert.equal(a.w2.getAddress(), addr); - assert.equal(a.w3.getAddress(), addr); + assert.equal(w1.getAddress(), addr); + assert.equal(w2.getAddress(), addr); + assert.equal(w3.getAddress(), addr); - var paddr = a.w1.getProgramAddress(); - assert.equal(a.w1.getProgramAddress(), paddr); - assert.equal(a.w2.getProgramAddress(), paddr); - assert.equal(a.w3.getProgramAddress(), paddr); + var paddr = w1.getProgramAddress(); + assert.equal(w1.getProgramAddress(), paddr); + assert.equal(w2.getProgramAddress(), paddr); + assert.equal(w3.getProgramAddress(), paddr); // Add a shared unspent transaction to our wallets var utx = bcoin.mtx(); @@ -672,7 +607,7 @@ describe('Wallet', function() { utx.ts = 1; utx.height = 1; - assert.equal(a.w1.receiveDepth, 1); + assert.equal(w1.receiveDepth, 1); wdb.addTX(utx, function(err) { assert.ifError(err); @@ -681,91 +616,83 @@ describe('Wallet', function() { wdb.addTX(utx, function(err) { assert.ifError(err); - getInfo(function(err, a) { - assert.ifError(err); - assert.equal(a.w1.receiveDepth, 2); - assert.equal(a.w1.changeDepth, 1); + assert.equal(w1.receiveDepth, 2); + assert.equal(w1.changeDepth, 1); - assert(a.w1.getAddress() !== addr); - addr = a.w1.getAddress(); - assert.equal(a.w1.getAddress(), addr); - assert.equal(a.w2.getAddress(), addr); - assert.equal(a.w3.getAddress(), addr); + assert(w1.getAddress() !== addr); + addr = w1.getAddress(); + assert.equal(w1.getAddress(), addr); + assert.equal(w2.getAddress(), addr); + assert.equal(w3.getAddress(), addr); // Create a tx requiring 2 signatures var send = bcoin.mtx(); - send.addOutput({ address: a.receive.getAddress(), value: 5460 }); + send.addOutput({ address: receive.getAddress(), value: 5460 }); assert(!send.verify(null, true, flags)); w1.fill(send, { rate: 10000, round: true }, function(err) { assert.ifError(err); w1.sign(send, function(err) { - assert.ifError(err); - - assert(!send.verify(null, true, flags)); - w2.sign(send, function(err) { - assert.ifError(err); - - assert(send.verify(null, true, flags)); - - assert.equal(a.w1.changeDepth, 1); - var change = a.w1.changeAddress.getAddress(); - assert.equal(a.w1.changeAddress.getAddress(), change); - assert.equal(a.w2.changeAddress.getAddress(), change); - assert.equal(a.w3.changeAddress.getAddress(), change); - - // Simulate a confirmation - send.ps = 0; - send.ts = 1; - send.height = 1; - - wdb.addTX(send, function(err) { assert.ifError(err); - wdb.addTX(send, function(err) { + + assert(!send.verify(null, true, flags)); + w2.sign(send, function(err) { assert.ifError(err); + + assert(send.verify(null, true, flags)); + + assert.equal(w1.changeDepth, 1); + var change = w1.changeAddress.getAddress(); + assert.equal(w1.changeAddress.getAddress(), change); + assert.equal(w2.changeAddress.getAddress(), change); + assert.equal(w3.changeAddress.getAddress(), change); + + // Simulate a confirmation + send.ps = 0; + send.ts = 1; + send.height = 1; + wdb.addTX(send, function(err) { assert.ifError(err); - getInfo(function(err, a) { + wdb.addTX(send, function(err) { assert.ifError(err); + wdb.addTX(send, function(err) { + assert.ifError(err); - assert.equal(a.w1.receiveDepth, 2); - assert.equal(a.w1.changeDepth, 2); + assert.equal(w1.receiveDepth, 2); + assert.equal(w1.changeDepth, 2); - assert(a.w1.getAddress() === addr); - assert(a.w1.changeAddress.getAddress() !== change); - change = a.w1.changeAddress.getAddress(); - assert.equal(a.w1.changeAddress.getAddress(), change); - assert.equal(a.w2.changeAddress.getAddress(), change); - assert.equal(a.w3.changeAddress.getAddress(), change); + assert(w1.getAddress() === addr); + assert(w1.changeAddress.getAddress() !== change); + change = w1.changeAddress.getAddress(); + assert.equal(w1.changeAddress.getAddress(), change); + assert.equal(w2.changeAddress.getAddress(), change); + assert.equal(w3.changeAddress.getAddress(), change); - if (witness) - send.inputs[0].witness.items[2] = new Buffer([]); - else - send.inputs[0].script.code[2] = 0; + if (witness) + send.inputs[0].witness.items[2] = new Buffer([]); + else + send.inputs[0].script.code[2] = 0; - assert(!send.verify(null, true, flags)); - assert.equal(send.getFee(), 10000); + assert(!send.verify(null, true, flags)); + assert.equal(send.getFee(), 10000); - // w3 = bcoin.wallet.fromJSON(w3.toJSON()); - // assert.equal(a.w3.receiveDepth, 2); - // assert.equal(a.w3.changeDepth, 2); - //assert.equal(a.w3.getAddress(), addr); - //assert.equal(a.w3.changeAddress.getAddress(), change); + // w3 = bcoin.wallet.fromJSON(w3.toJSON()); + // assert.equal(w3.receiveDepth, 2); + // assert.equal(w3.changeDepth, 2); + //assert.equal(w3.getAddress(), addr); + //assert.equal(w3.changeAddress.getAddress(), change); - cb(); + cb(); + }); }); }); }); }); }); - }); - }); - }); - }); }); }); }); - }); }); }); }