diff --git a/lib/bcoin/txdb.js b/lib/bcoin/txdb.js index 0c9055fd..96c75878 100644 --- a/lib/bcoin/txdb.js +++ b/lib/bcoin/txdb.js @@ -52,7 +52,8 @@ function TXDB(db, options) { if (!options) options = {}; - this.db = db; + this.wdb = db; + this.db = db.db; this.options = options; this.network = bcoin.network.get(options.network); this.busy = false; @@ -568,21 +569,21 @@ TXDB.prototype._add = function add(tx, map, callback, force) { if (err) return callback(err); - self.emit('tx', tx, map); + self.wdb.sync(tx, map, function(err) { + if (err) + return callback(err); - if (updated) { - if (tx.ts !== 0) { - self.emit('confirmed', tx, map, function() { - self.emit('updated', tx, map); - return callback(null, true); - }); - return; + self.emit('tx', tx, map); + + if (updated) { + if (tx.ts !== 0) + self.emit('confirmed', tx, map); + + self.emit('updated', tx, map); } - self.emit('updated', tx, map); - } - - return callback(null, true); + return callback(null, true); + }); }); }); }); @@ -764,7 +765,11 @@ TXDB.prototype._confirm = function _confirm(tx, map, callback, force) { if (err) return callback(err); - self.emit('confirmed', tx, map, function() { + self.wdb.sync(tx, map, function(err) { + if (err) + return callback(err); + + self.emit('confirmed'); self.emit('tx', tx, map); return callback(null, true); diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 4a87b850..70821171 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -63,10 +63,11 @@ function Wallet(options) { if (!options.master) options.master = bcoin.hd.fromMnemonic(null, this.network); - if (!(options.master instanceof bcoin.hd)) + if (!(options.master instanceof bcoin.hd) && !(options.master instanceof MasterKey)) options.master = bcoin.hd.fromAny(options.master, this.network); - options.master = MasterKey.fromKey(options.master); + if (!(options.master instanceof MasterKey)) + options.master = MasterKey.fromKey(options.master); this.id = options.id || null; this.master = options.master || null; @@ -510,9 +511,6 @@ Wallet.prototype.deriveAddress = function deriveAddress(change, index) { path = change; if (path) { - // Map address to path - if (path.indexOf('/') === -1) - return; data = this.parsePath(path); } else { data = { @@ -1257,7 +1255,7 @@ Wallet.prototype.sign = function sign(tx, options, callback) { return callback(err); try { - key = self.master.decrypt(options.passphrase); + var master = self.master.decrypt(options.passphrase); } catch (e) { return callback(null, 0); } @@ -1265,12 +1263,17 @@ Wallet.prototype.sign = function sign(tx, options, callback) { for (i = 0; i < addresses.length; i++) { address = addresses[i]; - if (self.derivation === 'bip44') + key = master; + if (self.derivation === 'bip44') { key = key.deriveAccount44(self.accountIndex); - else if (self.derivation === 'bip45') + assert.equal(key.xpubkey, self.accountKey.xpubkey); + } else if (self.derivation === 'bip45') key = key.derivePurpose45(); + else + assert(false); key = key.derive(address.path); + assert(utils.equal(key.getPublicKey(), address.key)); total += address.sign(tx, key, options.index, options.type); } @@ -1706,3 +1709,537 @@ MasterKey.fromJSON = function fromJSON(json) { */ module.exports = Wallet; + +/*! + * wallet.js - wallet object for bcoin + * Copyright (c) 2014-2015, Fedor Indutny (MIT License) + * Copyright (c) 2014-2016, Christopher Jeffrey (MIT License). + * https://github.com/indutny/bcoin + */ + +var bcoin = require('./env'); +var EventEmitter = require('events').EventEmitter; +var utils = require('./utils'); +var assert = utils.assert; +var constants = bcoin.protocol.constants; +var BufferWriter = require('./writer'); + +/** + * HD BIP-44/45 wallet + * @exports Wallet + * @constructor + * @param {Object} options + * @param {WalletDB} options.db + * present, no coins will be available. + * @param {(HDPrivateKey|HDPublicKey)?} options.master - Master HD key. If not + * present, it will be generated. + * @param {Boolean?} options.witness - Whether to use witness programs. + * @param {Number?} options.accountIndex - The BIP44 account index (default=0). + * @param {Number?} options.receiveDepth - The index of the _next_ receiving + * address. + * @param {Number?} options.changeDepth - The index of the _next_ change + * address. + * @param {Boolean?} options.copayBIP45 - Use copay-style BIP45 if bip45 + * derivation is used. + * @param {Number?} options.lookahead - Amount of lookahead addresses + * (default=5). + * @param {String?} options.type - Type of wallet (pubkeyhash, multisig) + * (default=pubkeyhash). + * @param {String?} options.derivation - Derivation type (bip44, bip45) + * (default=bip44). + * @param {Boolean?} options.compressed - Whether to use compressed + * public keys (default=true). + * @param {Number?} options.m - `m` value for multisig. + * @param {Number?} options.n - `n` value for multisig. + * @param {String?} options.id - Wallet ID (used for storage) + * (default=account key "address"). + */ + +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 || bcoin.walletdb({ name: 'wtmp', db: 'memory' }); + this.id = id; +} + +utils.inherits(CWallet, EventEmitter); + +/** + * Open the wallet, wait for the database to load. + * @param {Function} callback + */ + +CWallet.prototype.open = function open(callback) { + var self = this; + + if (this.loaded) + return utils.nextTick(callback); + + if (this._loading) + return this.once('open', callback); + + this.db.register(this.id, this); + callback(); +}; + +/** + * Close the wallet, wait for the database to close. + * @method + * @param {Function} 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; +}; + +/** + * Add a public account/purpose key to the wallet for multisig. + * @param {HDPublicKey|Base58String} key - Account (bip44) or + * Purpose (bip45) key (can be in base58 form). + * @throws Error on non-hdkey/non-accountkey. + */ + +CWallet.prototype.addKey = function addKey(key, callback) { + this.db.addKey(this.id, key, callback); +}; + +/** + * Remove a public account/purpose key to the wallet for multisig. + * @param {HDPublicKey|Base58String} key - Account (bip44) or Purpose + * (bip45) key (can be in base58 form). + * @throws Error on non-hdkey/non-accountkey. + */ + +CWallet.prototype.removeKey = function removeKey(key, callback) { + this.db.removeKey(this.id, key, callback); +}; + +/** + * Get the wallet ID which is either the passed in `id` + * option, or the account/purpose key converted to an + * address with a prefix of `0x03be04` (`WLT`). + * @returns {Base58String} + */ + +CWallet.prototype.getID = function getID() { + return this.id; +}; + +/** + * Create a new receiving address (increments receiveDepth). + * @returns {KeyRing} + */ + +CWallet.prototype.createReceive = function createReceive(callback) { + return this.db.createAddress(this.id, false, callback); +}; + +/** + * Create a new change address (increments receiveDepth). + * @returns {KeyRing} + */ + +CWallet.prototype.createChange = function createChange(callback) { + return this.db.createAddress(this.id, true, callback); +}; + +/** + * Create a new address (increments depth). + * @param {Boolean} change + * @returns {KeyRing} + */ + +CWallet.prototype.createAddress = function createAddress(change, callback) { + return this.db.createAddress(this.id, change, callback); +}; + +/** + * Test whether the wallet posesses an address. + * @param {Base58Address} address + * @returns {Boolean} + */ + +CWallet.prototype.hasAddress = function hasAddress(address, callback) { + this.db.hasAddress(this.id, address, callback); +}; + +/** + * Fill a transaction with inputs, estimate + * transaction size, calculate fee, and add a change output. + * @see MTX#selectCoins + * @see MTX#fill + * @param {MTX} tx - _Must_ be a mutable transaction. + * @param {Object?} options + * @param {String?} options.selection - Coin selection priority. Can + * be `age`, `random`, or `all`. (default=age). + * @param {Boolean} options.round - Whether to round to the nearest + * kilobyte for fee calculation. + * See {@link TX#getMinFee} vs. {@link TX#getRoundFee}. + * @param {Rate} options.rate - Rate used for fee calculation. + * @param {Boolean} options.confirmed - Select only confirmed coins. + * @param {Boolean} options.free - Do not apply a fee if the + * transaction priority is high enough to be considered free. + * @param {Amount?} options.fee - Use a hard fee rather than calculating one. + * @param {Number|Boolean} options.subtractFee - Whether to subtract the + * fee from existing outputs rather than adding more inputs. + */ + +CWallet.prototype.fill = function fill(tx, options, callback) { + this.db.fill(this.id, tx, options, callback); +}; + +/** + * Fill transaction with coins (accesses db). + * @param {TX} tx + * @param {Function} callback - Returns [Error, {@link TX}]. + */ + +CWallet.prototype.fillCoins = function fillCoins(tx, callback) { + this.db.fillHistory(this.id, tx, callback); +}; + +/** + * Get a coin from the wallet (accesses db). + * @param {Hash} hash + * @param {Number} index + * @param {Function} callback - Returns [Error, {@link Coin}]. + */ + +CWallet.prototype.getCoin = function getCoin(hash, index, callback) { + this.db.getCoin(hash, index, callback); +}; + +/** + * Get a transaction from the wallet (accesses db). + * @param {Hash} hash + * @param {Function} callback - Returns [Error, {@link TX}]. + */ + +CWallet.prototype.getTX = function getTX(hash, callback) { + this.db.getTX(hash, callback); +}; + +/** + * Build a transaction, fill it with outputs and inputs, + * sort the members according to BIP69, set locktime, + * and sign it (accesses db). + * @param {Object} options - See {@link CWallet#fill options}. + * @param {Object[]} outputs - See {@link Script.createOutputScript}. + * @param {Function} callback - Returns [Error, {@link MTX}]. + */ + +CWallet.prototype.createTX = function createTX(options, outputs, callback) { + this.db.createTX(this.id, options, outputs, callback); +}; + +/** + * Get path by address. + * @param {Base58Address} address - Base58 address. + */ + +CWallet.prototype.getPath = function getPath(address, callback) { + if (!address || typeof address !== 'string') + return callback(); + this.db.getPath(this.id, address, callback); +}; + +/** + * Get a redeem script or witness script by hash. + * @param {Hash} hash - Can be a ripemd160 or a sha256. + * @returns {Script} + */ + +CWallet.prototype.getRedeem = function getRedeem(hash, callback) { + var self = this; + var address; + + if (typeof hash === 'string') + hash = new Buffer(hash, 'hex'); + + this.getPath(hash.toString('hex'), function(err, path) { + if (err) + return callback(err); + + if (!path) + return callback(); + + address = self.deriveAddress(path); + + if (address.program && hash.length === 20) { + if (utils.equal(hash, address.programHash)) + return callback(null, address.program); + } + + return callback(null, address.script); + }); +}; + +/** + * Zap stale TXs from wallet (accesses db). + * @param {Number} now - Current time (unix time). + * @param {Number} age - Age threshold (unix time, default=72 hours). + * @param {Function} callback - Returns [Error]. + */ + +CWallet.prototype.zap = function zap(now, age, callback) { + return this.db.zapWallet(this.id, now, age, callback); +}; + +/** + * Build input scripts templates for a transaction (does not + * sign, only creates signature slots). Only builds scripts + * for inputs that are redeemable by this wallet. + * @param {MTX} tx + * @param {Number?} index - Index of input. If not present, + * it will attempt to sign all redeemable inputs. + * @returns {Number} Total number of scripts built. + */ + +CWallet.prototype.scriptInputs = function scriptInputs(tx, callback) { + this.db.scriptInputs(this.id, tx, callback); +}; + +/** + * Build input scripts and sign inputs for a transaction. Only attempts + * to build/sign inputs that are redeemable by this wallet. + * @param {MTX} tx + * @param {Number?} index - Index of input. If not present, + * it will attempt to build and sign all redeemable inputs. + * @param {SighashType?} type + * @returns {Number} Total number of inputs scripts built and signed. + */ + +CWallet.prototype.sign = function sign(tx, options, callback) { + this.db.sign(this.id, tx, options, callback); +}; + +/** + * Add a transaction to the wallets TX history (accesses db). + * @param {TX} tx + * @param {Function} callback + */ + +CWallet.prototype.addTX = function addTX(tx, callback) { + return this.db.addTX(tx, callback); +}; + +/** + * Get all transactions in transaction history (accesses db). + * @param {Function} callback - Returns [Error, {@link TX}[]]. + */ + +CWallet.prototype.getHistory = function getHistory(callback) { + return this.db.getHistory(this.id, callback); +}; + +/** + * Get all available coins (accesses db). + * @param {Function} callback - Returns [Error, {@link Coin}[]]. + */ + +CWallet.prototype.getCoins = function getCoins(callback) { + return this.db.getCoins(this.id, callback); +}; + +/** + * Get all pending/unconfirmed transactions (accesses db). + * @param {Function} callback - Returns [Error, {@link TX}[]]. + */ + +CWallet.prototype.getUnconfirmed = function getUnconfirmed(callback) { + return this.db.getUnconfirmed(this.id, callback); +}; + +/** + * Get wallet balance (accesses db). + * @param {Function} callback - Returns [Error, {@link Balance}]. + */ + +CWallet.prototype.getBalance = function getBalance(callback) { + return this.db.getBalance(this.id, callback); +}; + +/** + * Get last timestamp and height this wallet was active + * at (accesses db). Useful for resetting the chain + * to a certain height when in SPV mode. + * @param {Function} callback - Returns [Error, Number(ts), Number(height)]. + */ + +CWallet.prototype.getLastTime = function getLastTime(callback) { + return this.db.getLastTime(this.id, callback); +}; + +/** + * Get the last N transactions (accesses db). + * @param {Number} limit + * @param {Function} callback - Returns [Error, {@link TX}[]]. + */ + +CWallet.prototype.getLast = function getLast(limit, callback) { + return this.db.getLast(this.id, limit, callback); +}; + +/** + * Get a range of transactions between two timestamps (accesses db). + * @param {Object} options + * @param {Number} options.start + * @param {Number} options.end + * @param {Function} callback - Returns [Error, {@link TX}[]]. + */ + +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); +}; + +/** + * Convert the wallet to a more inspection-friendly object. + * @returns {Object} + */ + +CWallet.prototype.inspect = function inspect() { + return { + id: this.id, + type: this.type, + network: this.network.type, + m: this.m, + n: this.n, + keyAddress: this.initialized + ? this.keyAddress + : null, + scriptAddress: this.initialized + ? this.scriptAddress + : null, + programAddress: this.initialized + ? this.programAddress + : null, + witness: this.witness, + derivation: this.derivation, + copayBIP45: this.copayBIP45, + accountIndex: this.accountIndex, + receiveDepth: this.receiveDepth, + changeDepth: this.changeDepth, + master: this.master.toJSON(), + accountKey: this.accountKey.xpubkey, + keys: this.keys.map(function(key) { + return key.xpubkey; + }) + }; +}; + +/** + * Convert the wallet to an object suitable for + * serialization. Will automatically encrypt the + * master key based on the `passphrase` option. + * @returns {Object} + */ + +CWallet.prototype.toJSON = function toJSON() { + return { + v: 3, + name: 'wallet', + network: this.network.type, + id: this.id, + type: this.type, + m: this.m, + n: this.n, + initialized: this.initialized, + saved: this.saved, + witness: this.witness, + derivation: this.derivation, + copayBIP45: this.copayBIP45, + accountIndex: this.accountIndex, + receiveDepth: this.receiveDepth, + changeDepth: this.changeDepth, + master: this.master.toJSON(), + accountKey: this.accountKey.xpubkey, + keys: this.keys.map(function(key) { + return key.xpubkey; + }) + }; +}; + +/** + * Handle a deserialized JSON wallet object. + * @returns {Object} A "naked" wallet (a + * plain javascript object which is suitable + * for passing to the Wallet constructor). + * @param {Object} json + * @param {String?} passphrase + * @returns {Object} + * @throws Error on bad decrypt + */ + +CWallet.parseJSON = function parseJSON(json) { + assert.equal(json.v, 3); + assert.equal(json.name, 'wallet'); + + return { + network: json.network, + id: json.id, + type: json.type, + m: json.m, + n: json.n, + initialized: json.initialized, + saved: json.saved, + witness: json.witness, + derivation: json.derivation, + copayBIP45: json.copayBIP45, + accountIndex: json.accountIndex, + receiveDepth: json.receiveDepth, + changeDepth: json.changeDepth, + master: MasterKey.fromJSON(json.master), + accountKey: bcoin.hd.fromBase58(json.accountKey), + keys: json.keys.map(function(key) { + return bcoin.hd.fromBase58(key); + }) + }; +}; + +/** + * Instantiate a Wallet from a + * jsonified wallet object. + * @param {Object} json - The jsonified wallet object. + * @returns {Wallet} + */ + +CWallet.fromJSON = function fromJSON(json) { + return new CWallet(CWallet.parseJSON(json)); +}; + +/** + * Test an object to see if it is a CWallet. + * @param {Object} obj + * @returns {Boolean} + */ + +CWallet.isCWallet = function isCWallet(obj) { + return obj + && typeof obj.receiveDepth === 'number' + && obj.deriveAddress === 'function'; +}; + +/* + * Expose + */ + +module.exports = Wallet; +bcoin.cwallet = CWallet; +module.exports.CWallet = CWallet; diff --git a/lib/bcoin/walletdb.js b/lib/bcoin/walletdb.js index 19b52620..95b0b45e 100644 --- a/lib/bcoin/walletdb.js +++ b/lib/bcoin/walletdb.js @@ -108,7 +108,7 @@ WalletDB.prototype._init = function _init() { writeBufferSize: 4 << 20 }); - this.tx = new bcoin.txdb(this.db, { + this.tx = new bcoin.txdb(this, { network: this.network, indexExtra: true, indexAddress: true, @@ -141,19 +141,11 @@ WalletDB.prototype._init = function _init() { }); }); - this.tx.on('confirmed', function(tx, map, callback) { + this.tx.on('confirmed', function(tx, map) { self.emit('confirmed', tx, map); map.all.forEach(function(id) { self.fire(id, 'confirmed', tx); }); - utils.forEachSerial(map.output, function(id, next) { - self.syncOutputDepth(id, tx, next); - }, function(err) { - if (err) - self.emit('error', err); - if (callback) - callback(); - }); }); this.tx.on('unconfirmed', function(tx, map) { @@ -197,6 +189,13 @@ WalletDB.prototype._init = function _init() { }); }; +WalletDB.prototype.sync = function sync(tx, map, callback) { + var self = this; + utils.forEachSerial(map.output, function(id, next) { + self.syncOutputDepth(id, tx, next); + }, callback); +}; + /** * Open the walletdb, wait for the database to load. * @param {Function} callback @@ -221,50 +220,6 @@ WalletDB.prototype.destroy = function destroy(callback) { this.db.close(callback); }; -/** - * Sync address depths based on a transaction's outputs. - * This is used for deriving new addresses when - * a confirmed transaction is seen. - * @param {WalletID} id - * @param {TX} tx - * @param {Function} callback - */ - -WalletDB.prototype.syncOutputDepth = function syncOutputDepth(id, tx, callback) { - var self = this; - - callback = utils.ensure(callback); - - this.get(id, function(err, wallet) { - if (err) - return callback(err); - - if (!wallet) - return callback(new Error('No wallet.')); - - wallet.syncOutputDepth(tx, function(err) { - if (err) { - wallet.destroy(); - return callback(err); - } - - wallet.destroy(); - - if (!self.providers[id]) - return callback(); - - self.providers[id].forEach(function(provider) { - provider.receiveDepth = wallet.receiveDepth; - provider.changeDepth = wallet.changeDepth; - provider.receiveAddress = wallet.receiveAddress; - provider.changeAddress = wallet.changeAddress; - }); - - callback(); - }); - }); -}; - /** * Derive an address. * @param {WalletID} id @@ -272,89 +227,87 @@ WalletDB.prototype.syncOutputDepth = function syncOutputDepth(id, tx, callback) * @param {Function} callback */ -WalletDB.prototype.createAddress = function createAddress(id, change, callback) { +WalletDB.prototype.rpc = function rpc(id, callback, method) { var self = this; - var address; callback = utils.ensure(callback); - this.get(id, function(err, wallet) { + this.get(id, function(err, _, wallet) { if (err) return callback(err); if (!wallet) return callback(new Error('No wallet.')); - wallet.createAddress(change, function(err) { - if (err) { - wallet.destroy(); - return callback(err); - } - - wallet.destroy(); - - if (!self.providers[id]) - return callback(); - - self.providers[id].forEach(function(provider) { - provider.receiveDepth = wallet.receiveDepth; - provider.changeDepth = wallet.changeDepth; - provider.receiveAddress = wallet.receiveAddress; - provider.changeAddress = wallet.changeAddress; - }); - - callback(); - }); + method(wallet); }); }; -/** - * Add a public account/purpose key to the wallet for multisig. - * @param {WalletID} id - * @param {HDPublicKey|Base58String} key - Account (bip44) or - * Purpose (bip45) key (can be in base58 form). - * @param {Function} callback - */ +WalletDB.prototype.syncOutputDepth = function syncOutputDepth(id, tx, callback) { + this.rpc(id, callback, function(wallet) { + wallet.syncOutputDepth(tx, callback); + }); +}; -WalletDB.prototype.modifyKey = function modifyKey(id, key, remove, callback) { - var self = this; +WalletDB.prototype.createAddress = function createAddress(id, change, callback) { + this.rpc(id, callback, function(wallet) { + wallet.createAddress(change, callback); + }); +}; - callback = utils.ensure(callback); +WalletDB.prototype.getReceiveAddress = function getReceiveAddress(id, callback) { + this.rpc(id, callback, function(wallet) { + callback(null, wallet.receiveAddress); + }); +}; - this.get(id, function(err, json) { - if (err) - return callback(err); +WalletDB.prototype.getChangeAddress = function getChangeAddress(id, callback) { + this.rpc(id, callback, function(wallet) { + callback(null, wallet.changeAddress); + }); +}; - if (!wallet) - return callback(new Error('No wallet.')); +WalletDB.prototype.fill = function fill(id, tx, options, callback) { + this.rpc(id, callback, function(wallet) { + wallet.fill(tx, options, callback); + }); +}; - function done(err) { - if (err) { - wallet.destroy(); - return callback(err); - } +WalletDB.prototype.scriptInputs = function scriptInputs(id, tx, callback) { + this.rpc(id, callback, function(wallet) { + wallet.scriptInputs(tx, callback); + }); +}; - wallet.destroy(); +WalletDB.prototype.sign = function sign(id, tx, options, callback) { + if (typeof options === 'function') { + callback = options; + options = {}; + } - if (!self.providers[id]) - return callback(); + if (typeof options === 'string' || Buffer.isBuffer(options)) + options = { passphrase: options }; - self.providers[id].forEach(function(provider) { - provider.keys = wallet.keys.slice(); - provider.initialized = wallet.initialized; - }); + this.rpc(id, callback, function(wallet) { + wallet.sign(tx, options, callback); + }); +}; - callback(); - } +WalletDB.prototype.createTX = function createTX(id, options, outputs, callback) { + this.rpc(id, callback, function(wallet) { + wallet.createTX(options, outputs, callback); + }); +}; - try { - if (!remove) - wallet.addKey(key, done); - else - wallet.removeKey(key, done); - } catch (e) { - return done(e); - } +WalletDB.prototype.addKey = function addKey(id, key, callback) { + this.rpc(id, callback, function(wallet) { + wallet.addKey(key, callback); + }); +}; + +WalletDB.prototype.removeKey = function removeKey(id, key, callback) { + this.rpc(id, callback, function(wallet) { + wallet.removeKey(key, callback); }); }; @@ -454,7 +407,7 @@ WalletDB.prototype.get = function get(id, callback) { if (err) return callback(err); - return callback(null, wallet); + return callback(null, new bcoin.cwallet(wallet.id, self), wallet); }); }); }; @@ -510,6 +463,7 @@ WalletDB.prototype.create = function create(options, callback) { return callback( new Error('`' + options.id + '` already exists.'), null, + null, json); } @@ -524,7 +478,7 @@ WalletDB.prototype.create = function create(options, callback) { if (err) return callback(err); - return callback(null, wallet); + return callback(null, new bcoin.cwallet(wallet.id, self), wallet); }); } @@ -543,12 +497,12 @@ WalletDB.prototype.create = function create(options, callback) { WalletDB.prototype.ensure = function ensure(options, callback) { var self = this; - return this.create(options, function(err, wallet, json) { + return this.create(options, function(err, cwallet, wallet, json) { if (err && !json) return callback(err); - if (wallet) - return callback(null, wallet); + if (cwallet) + return callback(null, cwallet); assert(json); @@ -564,7 +518,7 @@ WalletDB.prototype.ensure = function ensure(options, callback) { if (err) return callback(err); - return callback(null, wallet); + return callback(null, new bcoin.cwallet(wallet.id, self), wallet); }); }); }; diff --git a/test/wallet-test.js b/test/wallet-test.js index 37e4516f..b5ba0428 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -82,37 +82,40 @@ describe('Wallet', function() { if (witness) flags |= bcoin.protocol.constants.flags.VERIFY_WITNESS; - wdb.create({ witness: witness }, function(err, w) { + wdb.create({ witness: witness }, function(err, w, w2) { assert.ifError(err); - if (witness) - assert(bcoin.address.parseBase58(w.getAddress()).type === 'witnesspubkeyhash'); - else - assert(bcoin.address.parseBase58(w.getAddress()).type === 'pubkeyhash'); + w.getReceiveAddress(function(err, a) { + if (witness) + assert(bcoin.address.parseBase58(a.getAddress()).type === 'witnesspubkeyhash'); + else + assert(bcoin.address.parseBase58(a.getAddress()).type === 'pubkeyhash'); - // 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() - }] - }); + // 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() + }] + }); - src.addInput(dummyInput); + src.addInput(dummyInput); - var tx = bcoin.mtx() - .addInput(src, 0) - .addOutput(w.getAddress(), 5460); + var tx = bcoin.mtx() + .addInput(src, 0) + .addOutput(a.getAddress(), 5460); - w.sign(tx, function(err) { - assert.ifError(err); - assert(tx.verify(null, true, flags)); - cb(); + w.sign(tx, function(err) { + assert.ifError(err); + utils.print(tx); + assert(tx.verify(null, true, flags)); + cb(); + }); }); }); } @@ -171,21 +174,25 @@ 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(w, 50000).addOutput(w, 1000); + var t1 = bcoin.mtx().addOutput(aw, 50000).addOutput(aw, 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(w, 24000) - .addOutput(w, 24000); + .addOutput(aw, 24000) + .addOutput(aw, 24000); di = t2.inputs[0]; // balance: 49000 // w.sign(t2); @@ -193,27 +200,27 @@ describe('Wallet', function() { assert.ifError(err); var t3 = bcoin.mtx().addInput(t1, 1) // 1000 .addInput(t2, 0) // 24000 - .addOutput(w, 23000); + .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(w, 11000) - .addOutput(w, 11000); + .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(f, 10000); + .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(w, 500); + .addOutput(aw, 500); // Script inputs but do not sign // w.scriptInputs(fake); w.scriptInputs(fake, function(err) { @@ -262,7 +269,7 @@ describe('Wallet', function() { return tx.hash('hex') === f1.hash('hex'); })); - var w2 = bcoin.wallet.fromJSON(w.toJSON()); + //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'); @@ -278,6 +285,8 @@ describe('Wallet', function() { }); }); }); + }); + }); }); }); }); @@ -305,16 +314,20 @@ 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(w1, 5460) - .addOutput(w1, 5460) - .addOutput(w1, 5460) - .addOutput(w1, 5460); + .addOutput(aw1, 5460) + .addOutput(aw1, 5460) + .addOutput(aw1, 5460) + .addOutput(aw1, 5460); t1.addInput(dummyInput); @@ -323,7 +336,7 @@ describe('Wallet', function() { assert.ifError(err); // Create new transaction - var t2 = bcoin.mtx().addOutput(w2, 5460); + var t2 = bcoin.mtx().addOutput(aw2, 5460); w1.fill(t2, { rate: 10000, round: true }, function(err) { assert.ifError(err); w1.sign(t2, function(err) { @@ -338,7 +351,7 @@ describe('Wallet', function() { assert.equal(t2.getFee(), 10920); // Create new transaction - var t3 = bcoin.mtx().addOutput(w2, 15000); + var t3 = bcoin.mtx().addOutput(aw2, 15000); w1.fill(t3, { rate: 10000, round: true }, function(err) { assert(err); assert.equal(err.requiredFunds, 25000); @@ -347,22 +360,28 @@ 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(w1, 5460) - .addOutput(w1, 5460) - .addOutput(w1, 5460) - .addOutput(w1, 5460); + .addOutput(aw1, 5460) + .addOutput(aw1, 5460) + .addOutput(aw1, 5460) + .addOutput(aw1, 5460); t1.addInput(dummyInput); @@ -371,7 +390,7 @@ describe('Wallet', function() { assert.ifError(err); // Create new transaction - var t2 = bcoin.mtx().addOutput(w2, 5460); + var t2 = bcoin.mtx().addOutput(aw2, 5460); w1.fill(t2, { rate: 10000 }, function(err) { assert.ifError(err); w1.sign(t2, function(err) { @@ -393,7 +412,7 @@ describe('Wallet', function() { // Create new transaction wdb.addTX(t2, function(err) { assert.ifError(err); - var t3 = bcoin.mtx().addOutput(w2, 15000); + var t3 = bcoin.mtx().addOutput(aw2, 15000); w1.fill(t3, { rate: 10000 }, function(err) { assert(err); cb(); @@ -404,32 +423,40 @@ describe('Wallet', function() { }); }); }); + }); + }); }); 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(w1, 5460) - .addOutput(w1, 5460) - .addOutput(w1, 5460) - .addOutput(w1, 5460); + .addOutput(aw1, 5460) + .addOutput(aw1, 5460) + .addOutput(aw1, 5460) + .addOutput(aw1, 5460); t1.addInput(dummyInput); // Fake TX should temporarly change output // Coinbase var t2 = bcoin.mtx() - .addOutput(w2, 5460) - .addOutput(w2, 5460) - .addOutput(w2, 5460) - .addOutput(w2, 5460); + .addOutput(aw2, 5460) + .addOutput(aw2, 5460) + .addOutput(aw2, 5460) + .addOutput(aw2, 5460); t2.addInput(dummyInput); // Fake TX should temporarly change output @@ -441,7 +468,7 @@ describe('Wallet', function() { // Create our tx with an output var tx = bcoin.mtx(); - tx.addOutput(to, 5460); + tx.addOutput(ato, 5460); var cost = tx.getOutputValue(); var total = cost * constants.tx.MIN_FEE; @@ -452,7 +479,7 @@ describe('Wallet', function() { assert.ifError(err); // Add dummy output (for `left`) to calculate maximum TX size - tx.addOutput(w1, 0); + tx.addOutput(aw1, 0); // Add our unspent inputs to sign tx.addInput(coins1[0]); @@ -507,6 +534,9 @@ describe('Wallet', function() { }); }); }); + }); + }); + }); }); function multisig(witness, bullshitNesting, cb) {