diff --git a/lib/bcoin/env.js b/lib/bcoin/env.js index 6b415070..f2963959 100644 --- a/lib/bcoin/env.js +++ b/lib/bcoin/env.js @@ -116,7 +116,6 @@ function Environment(options) { this.options = options; - this._ensured = false; this._debug = null; this.isBrowser = utils.isBrowser; @@ -211,11 +210,8 @@ Environment.prototype.set = function set(options) { options.prefix = options.prefix || process.env.BCOIN_PREFIX; - if (!options.prefix) { + if (!options.prefix) options.prefix = utils.HOME + '/.bcoin'; - if (options.network !== 'main') - options.prefix += '/' + options.network; - } if (!options.db) options.db = process.env.BCOIN_DB; @@ -244,8 +240,12 @@ Environment.prototype.set = function set(options) { if (options.workerTime == null && process.env.BCOIN_WORKER_TIMEOUT != null) options.workerTimeout = +process.env.BCOIN_WORKER_TIMEOUT; - if (options.debugFile && typeof options.debugFile !== 'string') - options.debugFile = options.prefix + '/debug.log'; + if (options.debugFile && typeof options.debugFile !== 'string') { + options.debugFile = options.prefix; + if (options.network !== 'main') + options.debugFile += '/' + options.network; + options.debugFile += '/debug.log'; + } this.prefix = options.prefix; this.networkType = options.network; @@ -272,23 +272,26 @@ Environment.prototype.set = function set(options) { }; /** - * Ensure the `prefix`. - * @private + * Ensure a directory. + * @param {String} path + * @param {Boolean?} dirname */ -Environment.prototype.ensurePrefix = function ensurePrefix() { +Environment.prototype.mkdir = function mkdir(path, dirname) { if (this.isBrowser) return; - if (this._ensured) + path = normalize(path, dirname); + + if (!mkdir.paths) + mkdir.paths = {}; + + if (mkdir.paths[path]) return; - this._ensured = true; + mkdir.paths[path] = true; - mkdirp(this.prefix); - - if (this.profile) - mkdirp(this.prefix + '/profiler'); + return mkdirp(path); }; /** @@ -369,7 +372,7 @@ Environment.prototype.write = function write(msg) { return; if (!this._debug) { - this.ensurePrefix(); + this.mkdir(this.debugFile, true); this._debug = fs.createWriteStream(this.debugFile, { flags: 'a' }); } @@ -385,6 +388,25 @@ Environment.prototype.now = function now() { return this.time.now(); }; +/** + * Normalize a path. + * @param {String} path + * @param {Boolean?} dirname + */ + +function normalize(path, dirname) { + var parts; + + path = path.replace(/\\/g, '/'); + path = path.replace(/\/+$/, ''); + parts = path.split(/\/+/); + + if (dirname) + parts.pop(); + + return parts.join('/'); +} + /** * Create a full directory structure. * @param {String} path diff --git a/lib/bcoin/hd.js b/lib/bcoin/hd.js index 9fccf681..e7538c51 100644 --- a/lib/bcoin/hd.js +++ b/lib/bcoin/hd.js @@ -456,9 +456,9 @@ function HDPrivateKey(options) { this.publicKey = ec.publicKeyCreate(options.privateKey, true); this.fingerPrint = null; - this.mnemonic = options.mnemonic; + this.mnemonic = options.mnemonic || null; - this._xprivkey = options.xprivkey; + this._xprivkey = options.xprivkey || null; this.hdPrivateKey = this; this._hdPublicKey = null; @@ -692,6 +692,24 @@ HDPrivateKey.prototype.derivePath = function derivePath(path) { return key; }; +/** + * Compare a key against an object. + * @param {Object} obj + * @returns {Boolean} + */ + +HDPrivateKey.prototype.equal = function equal(obj) { + if (!HDPrivateKey.isHDPrivateKey(obj)) + return false; + + return this.network === obj.network + && this.depth === obj.depth + && utils.equal(this.parentFingerPrint, obj.parentFingerPrint) + && this.childIndex === obj.childIndex + && utils.equal(this.chainCode, obj.chainCode) + && utils.equal(this.privateKey, obj.privateKey); +}; + /** * Create an hd private key from a seed. * @param {Buffer|Mnemonic|Object} options - A seed, @@ -1021,8 +1039,8 @@ HDPrivateKey.fromJSON = function fromJSON(json, passphrase) { HDPrivateKey.isHDPrivateKey = function isHDPrivateKey(obj) { return obj - && obj.hdPublicKey - && obj.hdPublicKey !== obj + && obj.privateKey + && Buffer.isBuffer(obj.chainCode) && typeof obj.derive === 'function'; }; @@ -1219,6 +1237,24 @@ HDPublicKey.prototype.derivePath = function derivePath(path) { return key; }; +/** + * Compare a key against an object. + * @param {Object} obj + * @returns {Boolean} + */ + +HDPublicKey.prototype.equal = function equal(obj) { + if (!HDPublicKey.isHDPublicKey(obj)) + return false; + + return this.network === obj.network + && this.depth === obj.depth + && utils.equal(this.parentFingerPrint, obj.parentFingerPrint) + && this.childIndex === obj.childIndex + && utils.equal(this.chainCode, obj.chainCode) + && utils.equal(this.publicKey, obj.publicKey); +}; + /** * Convert key to a more json-friendly object. * @method @@ -1344,8 +1380,8 @@ HDPublicKey.fromBase58 = function fromBase58(xkey) { HDPublicKey.isHDPublicKey = function isHDPublicKey(obj) { return obj - && obj.hdPublicKey - && obj.hdPublicKey === obj + && !obj.privateKey + && Buffer.isBuffer(obj.chainCode) && typeof obj.derive === 'function'; }; diff --git a/lib/bcoin/ldb.js b/lib/bcoin/ldb.js index 04e512ed..90ebeac1 100644 --- a/lib/bcoin/ldb.js +++ b/lib/bcoin/ldb.js @@ -10,6 +10,7 @@ var bcoin = require('./env'); var LowlevelUp = require('./lowlevelup'); var utils = bcoin.utils; +var assert = utils.assert; var db = {}; /** @@ -28,16 +29,13 @@ var db = {}; */ function ldb(options) { - var file = ldb.getLocation(options); + options = ldb.parseOptions(options); - if (!db[file]) { - if (!options) - options = {}; - - db[file] = new LowlevelUp(file, { - keyEncoding: 'ascii', - valueEncoding: 'binary', + if (!db[options.location]) { + if (options.backend !== 'bst' && !bcoin.isBrowser) + bcoin.mkdir(options.location, true); + db[options.location] = new LowlevelUp(options.location, { // LevelDB and others createIfMissing: true, errorIfExists: false, @@ -54,11 +52,11 @@ function ldb(options) { mapSize: options.mapSize || 300 * (1024 << 20), writeMap: options.writeMap || false, - db: ldb.getBackend(options.db) + db: options.db }); } - return db[file]; + return db[options.location]; } /** @@ -67,7 +65,7 @@ function ldb(options) { * @returns {Object} */ -ldb.getName = function getName(db) { +ldb.getBackend = function getBackend(db) { var name, ext; if (!db) @@ -106,38 +104,38 @@ ldb.getName = function getName(db) { }; /** - * Get database location based on options. + * Parse options. * @param {Object} options - * @returns {String} Path. + * @returns {Object} */ -ldb.getLocation = function getLocation(options) { - var backend = ldb.getName(options.db); +ldb.parseOptions = function parseOptions(options) { + var network = bcoin.network.get(options.network); + var backend = ldb.getBackend(options.db); + var location = options.location; + var db; - if (options.location) - return options.location; - - return bcoin.prefix + '/' + options.name + '.' + backend.ext; -}; - -/** - * Require database backend module. - * @param {String} db - * @returns {Object} Module. - */ - -ldb.getBackend = function getBackend(db) { - var backend = ldb.getName(db); + if (!location) { + assert(typeof options.name === 'string', 'Name or location required.'); + location = bcoin.prefix; + if (network.type !== 'main') + location += '/' + network.type; + location += '/' + options.name + '.' + backend.ext; + } if (backend.name === 'bst') - return require('./bst'); + db = require('./bst'); + else if (bcoin.isBrowser) + db = require('level-js'); + else + db = require(options.db); - if (bcoin.isBrowser) - return require('level-js'); - - bcoin.ensurePrefix(); - - return require(backend.name); + return utils.merge({}, options, { + backend: backend.name, + ext: backend.ext, + location: location, + db: db + }); }; /** @@ -147,13 +145,12 @@ ldb.getBackend = function getBackend(db) { */ ldb.destroy = function destroy(options, callback) { - var file = ldb.getLocation(options); - var backend = ldb.getBackend(options.db); + options = ldb.parseOptions(options); - if (!backend.destroy) + if (!options.db.destroy) return utils.nextTick(callback); - backend.destroy(file, callback); + options.db.backend.destroy(options.location, callback); }; /** @@ -163,13 +160,12 @@ ldb.destroy = function destroy(options, callback) { */ ldb.repair = function repair(options, callback) { - var file = ldb.getLocation(options); - var backend = ldb.getBackend(options.db); + options = ldb.parseOptions(options); - if (!backend.repair) + if (!options.db.backend.repair) return utils.asyncify(callback)(new Error('Cannot repair.')); - backend.repair(file, callback); + options.db.backend.repair(options.location, callback); }; /* diff --git a/lib/bcoin/mtx.js b/lib/bcoin/mtx.js index b8edf49c..a9d59b5e 100644 --- a/lib/bcoin/mtx.js +++ b/lib/bcoin/mtx.js @@ -767,6 +767,24 @@ MTX.prototype.maxSize = function maxSize(options, force) { if (options.wallet) wallet = options.wallet; + function getRedeem(vector, hash) { + var redeem = vector.getRedeem(); + var address; + + if (redeem) + return redeem; + + if (!wallet) + return; + + address = wallet.receiveAddress; + + if (address.program && hash.length === 20) + return address.program; + + return address.script; + } + // Calculate the size, minus the input scripts. total = this.getBaseSize(); @@ -800,10 +818,7 @@ MTX.prototype.maxSize = function maxSize(options, force) { // here since it will be ignored by // the isMultisig clause. // OP_PUSHDATA2 [redeem] - redeem = wallet - ? wallet.getRedeem(prev.code[1]) - : input.script.getRedeem(); - + redeem = getRedeem(input.script, prev.code[1]); if (redeem) { prev = redeem; sz = prev.getSize(); @@ -834,10 +849,7 @@ MTX.prototype.maxSize = function maxSize(options, force) { hadWitness = true; if (prev.isWitnessScripthash()) { - redeem = wallet - ? wallet.getRedeem(prev.code[1]) - : input.witness.getRedeem(); - + redeem = getRedeem(input.witness, prev.code[1]); if (redeem) { prev = redeem; sz = prev.getSize(); diff --git a/lib/bcoin/profiler.js b/lib/bcoin/profiler.js index c5a1e0f4..64223012 100644 --- a/lib/bcoin/profiler.js +++ b/lib/bcoin/profiler.js @@ -23,7 +23,6 @@ function ensure() { if (bcoin.profile && !bcoin.isBrowser) { v8profiler = require('v8-' + 'profiler'); fs = require('f' + 's'); - bcoin.ensurePrefix(); } } @@ -109,6 +108,8 @@ Profile.prototype.save = function save(callback) { + self.name + '.cpuprofile'; + bcoin.mkdir(file, true); + fs.writeFile(file, result, callback); }); }; @@ -202,6 +203,8 @@ Snapshot.prototype.save = function save(callback) { + self.name + '.heapsnapshot'; + bcoin.mkdir(file, true); + fs.writeFile(file, result, callback); }); }; diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 70821171..704998ec 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -58,15 +58,18 @@ function Wallet(options) { this.options = options; this.network = bcoin.network.get(options.network); - this.db = options.db || bcoin.walletdb({ name: 'wtmp', db: 'memory' }); + this.db = options.db; + + if (!this.db) + this.db = new bcoin.walletdb({ name: 'wtmp', db: 'memory' }); if (!options.master) options.master = bcoin.hd.fromMnemonic(null, this.network); - if (!(options.master instanceof bcoin.hd) && !(options.master instanceof MasterKey)) + if (!bcoin.hd.isHD(options.master) && !MasterKey.isMasterKey(options.master)) options.master = bcoin.hd.fromAny(options.master, this.network); - if (!(options.master instanceof MasterKey)) + if (!MasterKey.isMasterKey(options.master)) options.master = MasterKey.fromKey(options.master); this.id = options.id || null; @@ -81,8 +84,7 @@ function Wallet(options) { this.copayBIP45 = options.copayBIP45 || false; this.lookahead = options.lookahead != null ? options.lookahead : 5; this.cosignerIndex = -1; - this.saved = options.saved || false; - this.initialized = options.initialized || false; + this.initialized = false; this.type = options.type || 'pubkeyhash'; this.derivation = options.derivation || 'bip44'; @@ -111,6 +113,8 @@ function Wallet(options) { key = key.derivePurpose45().hdPublicKey; else if (this.derivation === 'bip44') key = key.deriveAccount44(this.accountIndex).hdPublicKey; + else + assert(false); this.accountKey = key; } @@ -136,20 +140,20 @@ Wallet.prototype.init = function init(callback) { var addresses = []; var i; - this.saved = true; - // Waiting for more keys. if (this.keys.length !== this.n) { assert(!this.initialized); - return this.save(callback); + return this.db.open(function(err) { + if (err) + return callback(err); + self.save(callback); + }); } if (this.initialized) { - assert(this.saved); this.receiveAddress = this.deriveReceive(this.receiveDepth - 1); this.changeAddress = this.deriveChange(this.changeDepth - 1); - //return callback(); - return this.save(callback); + return this.db.open(callback); } this.initialized = true; @@ -172,74 +176,17 @@ Wallet.prototype.init = function init(callback) { addresses.push(this.receiveAddress); addresses.push(this.changeAddress); - return this.saveAddress(addresses, function(err) { + return this.db.open(function(err) { if (err) return callback(err); - return self.save(callback); - }); -}; - -/** - * Open the wallet, wait for the database to load. - * @param {Function} callback - */ - -Wallet.prototype.open = function open(callback) { - var self = this; - - if (this.loaded) - return utils.nextTick(callback); - - if (this._loading) - return this.once('open', callback); - - this._loading = true; - - this.db.register(this.id, this); - - this.db.open(function(err) { - if (err) - return self.emit('error', err); - - if (0) - if (self.saved) { - utils.nextTick(function() { - self.loaded = true; - self.emit('open'); - callback(); - }); - return; - } - - self.init(function(err) { - if (err) { - self.emit('error', err); + return self.saveAddress(addresses, function(err) { + if (err) return callback(err); - } - self.loaded = true; - self.emit('open'); - callback(); + return self.save(callback); }); }); }; -/** - * Close the wallet, wait for the database to close. - * @method - * @param {Function} callback - */ - -Wallet.prototype.close = -Wallet.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 @@ -248,14 +195,17 @@ Wallet.prototype.destroy = function destroy(callback) { */ Wallet.prototype._addKey = function addKey(key) { + var result = false; var index, i; assert(key, 'Key required.'); if (Array.isArray(key)) { - for (i = 0; i < key.length; i++) - this._addKey(key[i]); - return; + for (i = 0; i < key.length; i++) { + if (this._addKey(key[i])) + result = true; + } + return result; } if (key instanceof bcoin.wallet) { @@ -269,25 +219,26 @@ Wallet.prototype._addKey = function addKey(key) { if (key.hdPublicKey) key = key.hdPublicKey; - assert(key instanceof bcoin.hd, 'Must add HD keys to wallet.'); + if (!bcoin.hd.isHD(key)) + throw new Error('Must add HD keys to wallet.'); if (this.derivation === 'bip44') { - if (!key || !key.isAccount44()) + if (!key.isAccount44()) throw new Error('Must add HD account keys to BIP44 wallet.'); } else if (this.derivation === 'bip45') { - if (!key || !key.isPurpose45()) + if (!key.isPurpose45()) throw new Error('Must add HD purpose keys to BIP45 wallet.'); } for (i = 0; i < this.keys.length; i++) { - if (this.keys[i].xpubkey === key.xpubkey) { + if (this.keys[i].equal(key)) { index = i; break; } } if (index != null) - return; + return false; assert(!this._keysFinalized); @@ -295,11 +246,13 @@ Wallet.prototype._addKey = function addKey(key) { if (this.keys.length === this.n) this._finalizeKeys(); + + return true; }; Wallet.prototype.addKey = function addKey(key, callback) { - this._addKey(key); - this.init(callback); + if (this._addKey(key)) + this.init(callback); }; /** @@ -310,14 +263,17 @@ Wallet.prototype.addKey = function addKey(key, callback) { */ Wallet.prototype._removeKey = function removeKey(key) { + var result = false; var index, i; assert(!this._keysFinalized); if (Array.isArray(key)) { - for (i = 0; i < key.length; i++) - this._removeKey(key[i]); - return; + for (i = 0; i < key.length; i++) { + if (this._removeKey(key[i])) + result = true; + } + return result; } assert(key, 'Key required.'); @@ -333,27 +289,30 @@ Wallet.prototype._removeKey = function removeKey(key) { if (key.hdPublicKey) key = key.hdPublicKey; - assert(key instanceof bcoin.hd, 'Must add HD keys to wallet.'); + if (!bcoin.hd.isHD(key)) + throw new Error('Must add HD keys to wallet.'); if (this.derivation === 'bip44') { - if (!key || !key.isAccount44()) + if (!key.isAccount44()) throw new Error('Must add HD account keys to BIP44 wallet.'); } else if (this.derivation === 'bip45') { - if (!key || !key.isPurpose45()) + if (!key.isPurpose45()) throw new Error('Must add HD purpose keys to BIP45 wallet.'); } for (i = 0; i < this.keys.length; i++) { - if (this.keys[i].xpubkey === key.xpubkey) { + if (this.keys[i].equal(key)) { index = i; break; } } if (index == null) - return; + return false; this.keys.splice(index, 1); + + return true; }; Wallet.prototype.removeKey = function removeKey(key, callback) { @@ -362,8 +321,8 @@ Wallet.prototype.removeKey = function removeKey(key, callback) { if (this.keys.length === this.n) return callback(new Error('Cannot remove the fucking key now.')); - this._removeKey(key); - this.save(callback); + if (this._removeKey(key)) + this.save(callback); }; Wallet.prototype._finalizeKeys = function _finalizeKeys() { @@ -375,7 +334,7 @@ Wallet.prototype._finalizeKeys = function _finalizeKeys() { this.keys = utils.sortHDKeys(this.keys); for (i = 0; i < this.keys.length; i++) { - if (this.keys[i].xpubkey === this.accountKey.xpubkey) { + if (this.keys[i].equal(this.accountKey)) { this.cosignerIndex = i; break; } @@ -754,7 +713,7 @@ Wallet.prototype.fill = function fill(tx, options, callback) { rate: options.rate != null ? options.rate : self.network.getRate(), - //wallet: self, + wallet: self, m: self.m, n: self.n }); @@ -835,7 +794,8 @@ Wallet.prototype.createTX = function createTX(options, outputs, callback) { try { tx.addOutput(outputs[i]); } catch (e) { - return utils.asyncify(callback)(e); + callback = utils.asyncify(callback); + return callback(e); } } @@ -860,10 +820,15 @@ Wallet.prototype.createTX = function createTX(options, outputs, callback) { if (!tx.checkInputs(height)) return callback(new Error('CheckInputs failed.')); - if (!self.scriptInputs(tx)) - return callback(new Error('scriptInputs failed.')); + self.scriptInputs(tx, function(err, total) { + if (err) + return callback(err); - return callback(null, tx); + if (total === 0) + return callback(new Error('scriptInputs failed.')); + + return callback(null, tx); + }); }); }; @@ -1233,7 +1198,7 @@ Wallet.prototype.scriptInputs = function scriptInputs(tx, callback) { Wallet.prototype.sign = function sign(tx, options, callback) { var self = this; var total = 0; - var i, address, key; + var i, address, key, master; if (Array.isArray(tx)) { utils.forEachSerial(tx, function(tx, next) { @@ -1255,24 +1220,27 @@ Wallet.prototype.sign = function sign(tx, options, callback) { return callback(err); try { - var master = self.master.decrypt(options.passphrase); + master = self.master.decrypt(options.passphrase); } catch (e) { return callback(null, 0); } for (i = 0; i < addresses.length; i++) { address = addresses[i]; - key = master; + if (self.derivation === 'bip44') { key = key.deriveAccount44(self.accountIndex); assert.equal(key.xpubkey, self.accountKey.xpubkey); - } else if (self.derivation === 'bip45') + } else if (self.derivation === 'bip45') { key = key.derivePurpose45(); - else + assert.equal(key.xpubkey, self.accountKey.xpubkey); + } else { assert(false); + } key = key.derive(address.path); + assert(utils.equal(key.getPublicKey(), address.key)); total += address.sign(tx, key, options.index, options.type); @@ -1590,8 +1558,6 @@ Wallet.prototype.toJSON = function toJSON() { type: this.type, m: this.m, n: this.n, - initialized: this.initialized, - saved: this.saved, witness: this.witness, derivation: this.derivation, copayBIP45: this.copayBIP45, @@ -1627,8 +1593,6 @@ Wallet.parseJSON = function parseJSON(json) { type: json.type, m: json.m, n: json.n, - initialized: json.initialized, - saved: json.saved, witness: json.witness, derivation: json.derivation, copayBIP45: json.copayBIP45, @@ -1704,26 +1668,18 @@ MasterKey.fromJSON = function fromJSON(json) { }); }; +MasterKey.isMasterKey = function isMasterKey(obj) { + return obj + && obj.json + && typeof obj.decrypt === 'function'; +}; + /* * Expose */ 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 @@ -1764,7 +1720,7 @@ function CWallet(id, db) { EventEmitter.call(this); this.network = db.network; - this.db = db || bcoin.walletdb({ name: 'wtmp', db: 'memory' }); + this.db = db; this.id = id; } @@ -1963,28 +1919,7 @@ CWallet.prototype.getPath = function getPath(address, callback) { */ 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); - }); + ; }; /** @@ -2109,119 +2044,21 @@ CWallet.prototype.getReceiveAddress = function getReceiveAddress(callback) { return this.db.getReceiveAddress(this.id, callback); }; +CWallet.prototype.getInfo = function getInfo(callback) { + return this.db.get(this.id, function(err, cwallet, wallet) { + if (err) + return callback(err); + return callback(null, wallet); + }); +}; + /** * 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)); + return ''; }; /** @@ -2232,8 +2069,9 @@ CWallet.fromJSON = function fromJSON(json) { CWallet.isCWallet = function isCWallet(obj) { return obj - && typeof obj.receiveDepth === 'number' - && obj.deriveAddress === 'function'; + && obj.db + && obj.id + && obj.getInfo === 'function'; }; /* diff --git a/lib/bcoin/walletdb.js b/lib/bcoin/walletdb.js index 95b0b45e..ec725ff9 100644 --- a/lib/bcoin/walletdb.js +++ b/lib/bcoin/walletdb.js @@ -403,7 +403,7 @@ WalletDB.prototype.get = function get(id, callback) { return callback(e); } - wallet.open(function(err) { + wallet.init(function(err) { if (err) return callback(err); @@ -474,7 +474,7 @@ WalletDB.prototype.create = function create(options, callback) { options.db = self; wallet = new bcoin.wallet(options); - wallet.open(function(err) { + wallet.init(function(err) { if (err) return callback(err); @@ -514,7 +514,7 @@ WalletDB.prototype.ensure = function ensure(options, callback) { return callback(e); } - wallet.open(function(err) { + wallet.init(function(err) { if (err) return callback(err); diff --git a/test/chain-test.js b/test/chain-test.js index d8897059..81f0e50b 100644 --- a/test/chain-test.js +++ b/test/chain-test.js @@ -8,13 +8,14 @@ var opcodes = constants.opcodes; constants.tx.COINBASE_MATURITY = 0; describe('Chain', function() { - var chain, wallet, miner; + var chain, wallet, miner, walletdb; var competingTip, oldTip, ch1, ch2, cb1, cb2; this.timeout(5000); chain = new bcoin.chain({ name: 'chain-test', db: 'memory' }); - wallet = new bcoin.wallet(); + 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() @@ -40,13 +41,16 @@ describe('Chain', function() { value: utils.satoshi('25.0') }); redeemer.addOutput({ - address: wallet.createAddress().getAddress(), + address: wallet.deriveAddress(false, 100).getAddress(), value: utils.satoshi('5.0') }); redeemer.addInput(tx, 0); redeemer.setLocktime(chain.height); - wallet.sign(redeemer); - attempt.addTX(redeemer); + return wallet.sign(redeemer, function(err) { + assert.ifError(err); + attempt.addTX(redeemer); + callback(null, attempt.mineSync()); + }); } callback(null, attempt.mineSync()); }); @@ -68,7 +72,10 @@ describe('Chain', function() { } it('should open chain and miner', function(cb) { - miner.open(cb); + miner.open(function(err) { + assert.ifError(cb); + wallet.init(cb); + }); }); it('should mine a block', function(cb) { diff --git a/test/wallet-test.js b/test/wallet-test.js index b5ba0428..6e169c07 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -60,7 +60,7 @@ describe('Wallet', function() { it('should generate new key and address', function() { var w = bcoin.wallet(); - w.open(function(err) { + w.init(function(err) { assert.ifError(err); var addr = w.getAddress(); assert(addr); @@ -139,7 +139,7 @@ describe('Wallet', function() { m: 1, n: 2 }); - w.open(function(err) { + w.init(function(err) { assert.ifError(err); var k2 = bcoin.hd.fromMnemonic().deriveAccount44(0).hdPublicKey; w.addKey(k2, function(err) { @@ -556,6 +556,43 @@ 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_) { @@ -584,38 +621,42 @@ describe('Wallet', function() { receive = receive_; next(); }); - }, + } ], function(err) { assert.ifError(err); + getInfo(function(err, a) { + assert.ifError(err); utils.serial([ - w1.addKey.bind(w1, w2), - w1.addKey.bind(w1, w3), - w2.addKey.bind(w2, w1), - w2.addKey.bind(w2, w3), - w3.addKey.bind(w3, w1), - w3.addKey.bind(w3, w2) + 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) ], function(err) { assert.ifError(err); + getInfo(function(err, a) { + assert.ifError(err); // w3 = bcoin.wallet.fromJSON(w3.toJSON()); // Our p2sh address - var addr = w1.getAddress(); + var addr = a.w1.getAddress(); if (witness) assert(bcoin.address.parseBase58(addr).type === 'witnessscripthash'); else assert(bcoin.address.parseBase58(addr).type === 'scripthash'); - assert.equal(w1.getAddress(), addr); - assert.equal(w2.getAddress(), addr); - assert.equal(w3.getAddress(), addr); + assert.equal(a.w1.getAddress(), addr); + assert.equal(a.w2.getAddress(), addr); + assert.equal(a.w3.getAddress(), addr); - var paddr = w1.getProgramAddress(); - assert.equal(w1.getProgramAddress(), paddr); - assert.equal(w2.getProgramAddress(), paddr); - assert.equal(w3.getProgramAddress(), paddr); + var paddr = a.w1.getProgramAddress(); + assert.equal(a.w1.getProgramAddress(), paddr); + assert.equal(a.w2.getProgramAddress(), paddr); + assert.equal(a.w3.getProgramAddress(), paddr); // Add a shared unspent transaction to our wallets var utx = bcoin.mtx(); @@ -631,7 +672,7 @@ describe('Wallet', function() { utx.ts = 1; utx.height = 1; - assert.equal(w1.receiveDepth, 1); + assert.equal(a.w1.receiveDepth, 1); wdb.addTX(utx, function(err) { assert.ifError(err); @@ -640,18 +681,20 @@ describe('Wallet', function() { wdb.addTX(utx, function(err) { assert.ifError(err); - assert.equal(w1.receiveDepth, 2); - assert.equal(w1.changeDepth, 1); + getInfo(function(err, a) { + assert.ifError(err); + assert.equal(a.w1.receiveDepth, 2); + assert.equal(a.w1.changeDepth, 1); - assert(w1.getAddress() !== addr); - addr = w1.getAddress(); - assert.equal(w1.getAddress(), addr); - assert.equal(w2.getAddress(), addr); - assert.equal(w3.getAddress(), addr); + 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); // Create a tx requiring 2 signatures var send = bcoin.mtx(); - send.addOutput({ address: receive.getAddress(), value: 5460 }); + send.addOutput({ address: a.receive.getAddress(), value: 5460 }); assert(!send.verify(null, true, flags)); w1.fill(send, { rate: 10000, round: true }, function(err) { assert.ifError(err); @@ -665,11 +708,11 @@ describe('Wallet', function() { 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); + 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; @@ -682,32 +725,35 @@ describe('Wallet', function() { assert.ifError(err); wdb.addTX(send, function(err) { assert.ifError(err); + getInfo(function(err, a) { + assert.ifError(err); - assert.equal(w1.receiveDepth, 2); - assert.equal(w1.changeDepth, 2); + assert.equal(a.w1.receiveDepth, 2); + assert.equal(a.w1.changeDepth, 2); - 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); + 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); - 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(w3.receiveDepth, 2); - assert.equal(w3.changeDepth, 2); - //assert.equal(w3.getAddress(), addr); - //assert.equal(w3.changeAddress.getAddress(), change); + // 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); - cb(); + cb(); + }); }); }); }); @@ -715,8 +761,11 @@ describe('Wallet', function() { }); }); }); + }); + }); }); }); + }); }); }); }