master key. use locks to avoid race conditions in wallet.
This commit is contained in:
parent
c3ba9808b1
commit
64af74fe4a
@ -925,7 +925,7 @@ HDPrivateKey.parseBase58 = function parseBase58(xkey) {
|
||||
*/
|
||||
|
||||
HDPrivateKey.parseRaw = function parseRaw(raw) {
|
||||
var p = new BufferReader(raw, true);
|
||||
var p = new BufferReader(raw);
|
||||
var data = {};
|
||||
var i, type, prefix;
|
||||
|
||||
@ -1426,7 +1426,7 @@ HDPublicKey.parseBase58 = function parseBase58(xkey) {
|
||||
*/
|
||||
|
||||
HDPublicKey.parseRaw = function parseRaw(raw) {
|
||||
var p = new BufferReader(raw, true);
|
||||
var p = new BufferReader(raw);
|
||||
var data = {};
|
||||
|
||||
data.version = p.readU32BE();
|
||||
|
||||
@ -53,6 +53,7 @@ function Wallet(options) {
|
||||
this.options = options;
|
||||
this.network = bcoin.network.get(options.network);
|
||||
this.db = options.db;
|
||||
this.locker = new bcoin.locker(this);
|
||||
|
||||
if (!master)
|
||||
master = bcoin.hd.fromMnemonic(null, this.network);
|
||||
@ -146,6 +147,8 @@ Wallet.prototype.destroy = function destroy(callback) {
|
||||
|
||||
assert(!this.loading);
|
||||
|
||||
this.master.destroy();
|
||||
|
||||
try {
|
||||
this.db.unregister(this);
|
||||
} catch (e) {
|
||||
@ -219,12 +222,20 @@ Wallet.prototype.init = function init(callback) {
|
||||
*/
|
||||
|
||||
Wallet.prototype.addKey = function addKey(account, key, callback) {
|
||||
var unlock;
|
||||
|
||||
if (typeof key === 'function') {
|
||||
callback = key;
|
||||
key = account;
|
||||
account = 0;
|
||||
}
|
||||
|
||||
unlock = this.locker.lock(addKey, [account, key, callback]);
|
||||
if (!unlock)
|
||||
return;
|
||||
|
||||
callback = utils.wrap(callback, unlock);
|
||||
|
||||
this.getAccount(account, function(err, account) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
@ -233,7 +244,7 @@ Wallet.prototype.addKey = function addKey(account, key, callback) {
|
||||
return callback(new Error('Account not found.'));
|
||||
|
||||
account.addKey(key, callback);
|
||||
});
|
||||
}, true);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -244,12 +255,20 @@ Wallet.prototype.addKey = function addKey(account, key, callback) {
|
||||
*/
|
||||
|
||||
Wallet.prototype.removeKey = function removeKey(account, key, callback) {
|
||||
var unlock;
|
||||
|
||||
if (typeof key === 'function') {
|
||||
callback = key;
|
||||
key = account;
|
||||
account = 0;
|
||||
}
|
||||
|
||||
unlock = this.locker.lock(removeKey, [account, key, callback]);
|
||||
if (!unlock)
|
||||
return;
|
||||
|
||||
callback = utils.wrap(callback, unlock);
|
||||
|
||||
this.getAccount(account, function(err, account) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
@ -258,7 +277,7 @@ Wallet.prototype.removeKey = function removeKey(account, key, callback) {
|
||||
return callback(new Error('Account not found.'));
|
||||
|
||||
account.addKey(key, callback);
|
||||
});
|
||||
}, true);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -294,6 +313,25 @@ Wallet.prototype.setPassphrase = function setPassphrase(old, new_, callback) {
|
||||
return this.save(callback);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Lock the wallet, destroy decrypted key.
|
||||
*/
|
||||
|
||||
Wallet.prototype.lock = function lock() {
|
||||
this.master.destroy();
|
||||
};
|
||||
|
||||
/**
|
||||
* Unlock the key for `timeout` milliseconds.
|
||||
* @param {Buffer|String} passphrase
|
||||
* @param {Number?} [timeout=60000] - ms.
|
||||
*/
|
||||
|
||||
Wallet.prototype.unlock = function unlock(passphrase, timeout) {
|
||||
this.master.toKey(passphrase, timeout);
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate the wallet ID if none was passed in.
|
||||
* It is represented as `m/44'` (public) hashed
|
||||
@ -325,12 +363,18 @@ Wallet.prototype.getID = function getID() {
|
||||
* @param {Function} callback - Returns [Error, {@link Account}].
|
||||
*/
|
||||
|
||||
Wallet.prototype.createAccount = function createAccount(options, callback) {
|
||||
Wallet.prototype.createAccount = function createAccount(options, callback, force) {
|
||||
var self = this;
|
||||
var master, key;
|
||||
var master, key, unlock;
|
||||
|
||||
unlock = this.locker.lock(createAccount, [options, callback], force);
|
||||
if (!unlock)
|
||||
return;
|
||||
|
||||
callback = utils.wrap(callback, unlock);
|
||||
|
||||
try {
|
||||
master = this.master.toKey(options.passphrase);
|
||||
master = this.master.toKey(options.passphrase, options.timeout);
|
||||
} catch (e) {
|
||||
return callback(e);
|
||||
}
|
||||
@ -364,17 +408,33 @@ Wallet.prototype.createAccount = function createAccount(options, callback) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* List account names and indexes from the db.
|
||||
* @param {Function} callback - Returns [Error, Array].
|
||||
*/
|
||||
|
||||
Wallet.prototype.getAccounts = function getAccounts(callback) {
|
||||
this.db.getAccounts(this.id, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve an account from the database.
|
||||
* @param {Number|String} account
|
||||
* @param {Function} callback - Returns [Error, {@link Account}].
|
||||
*/
|
||||
|
||||
Wallet.prototype.getAccount = function getAccount(account, callback) {
|
||||
Wallet.prototype.getAccount = function getAccount(account, callback, force) {
|
||||
var unlock = this.locker.lock(getAccount, [account, callback], force);
|
||||
if (!unlock)
|
||||
return;
|
||||
|
||||
callback = utils.wrap(callback, unlock);
|
||||
|
||||
if (this.account) {
|
||||
if (account === 0 || account === 'default')
|
||||
return callback(null, this.account);
|
||||
}
|
||||
|
||||
return this.db.getAccount(this.id, account, callback);
|
||||
};
|
||||
|
||||
@ -414,11 +474,20 @@ Wallet.prototype.createChange = function createChange(account, callback) {
|
||||
*/
|
||||
|
||||
Wallet.prototype.createAddress = function createAddress(account, change, callback) {
|
||||
var unlock;
|
||||
|
||||
if (typeof change === 'function') {
|
||||
callback = change;
|
||||
change = account;
|
||||
account = 0;
|
||||
}
|
||||
|
||||
unlock = this.locker.lock(createAddress, [account, change, callback]);
|
||||
if (!unlock)
|
||||
return;
|
||||
|
||||
callback = utils.wrap(callback, unlock);
|
||||
|
||||
this.getAccount(account, function(err, account) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
@ -427,7 +496,7 @@ Wallet.prototype.createAddress = function createAddress(account, change, callbac
|
||||
return callback(new Error('Account not found.'));
|
||||
|
||||
account.createAddress(change, callback);
|
||||
});
|
||||
}, true);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -767,7 +836,13 @@ Wallet.prototype.syncOutputDepth = function syncOutputDepth(tx, callback) {
|
||||
var self = this;
|
||||
var accounts = {};
|
||||
var result = false;
|
||||
var i, path;
|
||||
var i, path, unlock;
|
||||
|
||||
unlock = this.locker.lock(syncOutputDepth, [tx, callback]);
|
||||
if (!unlock)
|
||||
return;
|
||||
|
||||
callback = utils.wrap(callback, unlock);
|
||||
|
||||
this.getOutputPaths(tx, function(err, paths) {
|
||||
if (err)
|
||||
@ -825,7 +900,7 @@ Wallet.prototype.syncOutputDepth = function syncOutputDepth(tx, callback) {
|
||||
next();
|
||||
});
|
||||
});
|
||||
});
|
||||
}, true);
|
||||
}, function(err) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
@ -876,6 +951,7 @@ Wallet.prototype.getRedeem = function getRedeem(hash, callback) {
|
||||
|
||||
/**
|
||||
* Zap stale TXs from wallet (accesses db).
|
||||
* @param {(Number|String)?} account
|
||||
* @param {Number} age - Age threshold (unix time, default=72 hours).
|
||||
* @param {Function} callback - Returns [Error].
|
||||
*/
|
||||
@ -906,6 +982,13 @@ Wallet.prototype.zap = function zap(account, age, callback) {
|
||||
Wallet.prototype.scan = function scan(getByAddress, callback) {
|
||||
var self = this;
|
||||
var total = 0;
|
||||
var unlock;
|
||||
|
||||
unlock = this.locker.lock(scan, [getByAddress, callback]);
|
||||
if (!unlock)
|
||||
return;
|
||||
|
||||
callback = utils.wrap(callback, unlock);
|
||||
|
||||
if (!this.initialized)
|
||||
return callback(new Error('Wallet is not initialized.'));
|
||||
@ -923,7 +1006,7 @@ Wallet.prototype.scan = function scan(getByAddress, callback) {
|
||||
|
||||
total += result;
|
||||
|
||||
self.createAccount(self.options, next);
|
||||
self.createAccount(self.options, next, true);
|
||||
});
|
||||
})(null, this.account);
|
||||
};
|
||||
@ -990,7 +1073,7 @@ Wallet.prototype.sign = function sign(tx, options, callback) {
|
||||
return callback(err);
|
||||
|
||||
try {
|
||||
master = self.master.toKey(options.passphrase);
|
||||
master = self.master.toKey(options.passphrase, options.timeout);
|
||||
} catch (e) {
|
||||
return callback(e);
|
||||
}
|
||||
@ -1526,7 +1609,10 @@ Wallet.isWallet = function isWallet(obj) {
|
||||
};
|
||||
|
||||
/**
|
||||
* BIP44 Account
|
||||
* Represents a BIP44 Account belonging to a {@link Wallet}.
|
||||
* Note that this object does not enforce locks. Any method
|
||||
* that does a write is internal API only and will lead
|
||||
* to race conditions if used elsewhere.
|
||||
* @exports Account
|
||||
* @constructor
|
||||
* @param {Object} options
|
||||
@ -1998,11 +2084,11 @@ Account.prototype.deriveAddress = function deriveAddress(change, index) {
|
||||
return new bcoin.keyring({
|
||||
network: this.network,
|
||||
key: key.publicKey,
|
||||
name: this.name,
|
||||
account: this.accountIndex,
|
||||
change: change,
|
||||
index: index,
|
||||
type: this.type,
|
||||
name: this.name,
|
||||
witness: this.witness,
|
||||
m: this.m,
|
||||
n: this.n,
|
||||
@ -2373,8 +2459,12 @@ Account.isAccount = function isAccount(obj) {
|
||||
&& obj.deriveAddress === 'function';
|
||||
};
|
||||
|
||||
/*
|
||||
* Master Key
|
||||
/**
|
||||
* Master BIP32 key which can exist
|
||||
* in an timed out encrypted state.
|
||||
* @exports Master
|
||||
* @constructor
|
||||
* @param {Object} options
|
||||
*/
|
||||
|
||||
function MasterKey(options) {
|
||||
@ -2386,30 +2476,101 @@ function MasterKey(options) {
|
||||
this.phrase = options.phrase;
|
||||
this.passphrase = options.passphrase;
|
||||
this.key = options.key || null;
|
||||
this.timer = null;
|
||||
this._destroy = this.destroy.bind(this);
|
||||
|
||||
assert(this.encrypted ? !this.key : this.key);
|
||||
}
|
||||
|
||||
MasterKey.prototype.toKey = function toKey(passphrase) {
|
||||
/**
|
||||
* Decrypt the key and set a timeout to destroy decrypted data.
|
||||
* @param {Buffer|String} passphrase - Zero this yourself.
|
||||
* @param {Number} [timeout=60000] timeout in ms.
|
||||
* @returns {HDPrivateKey}
|
||||
*/
|
||||
|
||||
MasterKey.prototype.toKey = function toKey(passphrase, timeout) {
|
||||
var self = this;
|
||||
var xprivkey;
|
||||
|
||||
if (this.key)
|
||||
return this.key;
|
||||
|
||||
if (this.encrypted) {
|
||||
assert(passphrase, 'Passphrase is required.');
|
||||
if (!this.key) {
|
||||
assert(this.encrypted);
|
||||
xprivkey = utils.decrypt(this.xprivkey, passphrase);
|
||||
} else {
|
||||
xprivkey = this.xprivkey;
|
||||
this.key = bcoin.hd.fromRaw(xprivkey);
|
||||
xprivkey.fill(0);
|
||||
this.start(timeout);
|
||||
}
|
||||
|
||||
return bcoin.hd.fromRaw(xprivkey);
|
||||
return this.key;
|
||||
};
|
||||
|
||||
MasterKey.prototype.decrypt = function decrypt(passphrase) {
|
||||
if (!this.encrypted)
|
||||
/**
|
||||
* Start the destroy timer.
|
||||
* @private
|
||||
* @param {Number} [timeout=60000] timeout in ms.
|
||||
*/
|
||||
|
||||
MasterKey.prototype.start = function start(timeout) {
|
||||
if (!timeout)
|
||||
timeout = 60000;
|
||||
|
||||
this.stop();
|
||||
|
||||
if (timeout === -1)
|
||||
return;
|
||||
|
||||
this.timer = setTimeout(this._destroy, timeout);
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop the destroy timer.
|
||||
* @private
|
||||
*/
|
||||
|
||||
MasterKey.prototype.stop = function stop() {
|
||||
if (this.timer != null) {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy the key by zeroing the
|
||||
* privateKey and chainCode. Stop
|
||||
* the timer if there is one.
|
||||
*/
|
||||
|
||||
MasterKey.prototype.destroy = function destroy() {
|
||||
if (!this.encrypted) {
|
||||
assert(this.timer == null);
|
||||
assert(this.key);
|
||||
return;
|
||||
}
|
||||
|
||||
this.stop();
|
||||
|
||||
if (this.key) {
|
||||
this.key.chainCode.fill(0);
|
||||
this.key.privateKey.fill(0);
|
||||
this.key = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Decrypt the key permanently.
|
||||
* @param {Buffer|String} passphrase - Zero this yourself.
|
||||
*/
|
||||
|
||||
MasterKey.prototype.decrypt = function decrypt(passphrase) {
|
||||
if (!this.encrypted) {
|
||||
assert(this.key);
|
||||
return;
|
||||
}
|
||||
|
||||
assert(passphrase, 'Passphrase is required.');
|
||||
|
||||
this.destroy();
|
||||
|
||||
this.encrypted = false;
|
||||
this.xprivkey = utils.decrypt(this.xprivkey, passphrase);
|
||||
|
||||
@ -2418,10 +2579,19 @@ MasterKey.prototype.decrypt = function decrypt(passphrase) {
|
||||
this.passphrase = utils.decrypt(this.passphrase, passphrase);
|
||||
}
|
||||
|
||||
this.key = this.toKey();
|
||||
this.key = bcoin.hd.fromRaw(this.xprivkey);
|
||||
};
|
||||
|
||||
/**
|
||||
* Encrypt the key permanently.
|
||||
* @param {Buffer|String} passphrase - Zero this yourself.
|
||||
*/
|
||||
|
||||
MasterKey.prototype.encrypt = function encrypt(passphrase) {
|
||||
var xprivkey = this.xprivkey;
|
||||
var phrase = this.phrase;
|
||||
var pass = this.passphrase;
|
||||
|
||||
if (this.encrypted)
|
||||
return;
|
||||
|
||||
@ -2429,14 +2599,23 @@ MasterKey.prototype.encrypt = function encrypt(passphrase) {
|
||||
|
||||
this.key = null;
|
||||
this.encrypted = true;
|
||||
this.xprivkey = utils.encrypt(this.xprivkey, passphrase);
|
||||
this.xprivkey = utils.encrypt(xprivkey, passphrase);
|
||||
xprivkey.fill(0);
|
||||
|
||||
if (this.phrase) {
|
||||
this.phrase = utils.encrypt(this.phrase, passphrase);
|
||||
this.passphrase = utils.encrypt(this.passphrase, passphrase);
|
||||
this.phrase = utils.encrypt(phrase, passphrase);
|
||||
this.passphrase = utils.encrypt(pass, passphrase);
|
||||
phrase.fill(0);
|
||||
pass.fill(0);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Serialize the key in the form of:
|
||||
* `[enc-flag][phrase-marker][phrase?][passphrase?][xprivkey]`
|
||||
* @returns {Buffer}
|
||||
*/
|
||||
|
||||
MasterKey.prototype.toRaw = function toRaw(writer) {
|
||||
var p = new BufferWriter(writer);
|
||||
|
||||
@ -2458,89 +2637,129 @@ MasterKey.prototype.toRaw = function toRaw(writer) {
|
||||
return p;
|
||||
};
|
||||
|
||||
MasterKey.fromRaw = function fromRaw(raw) {
|
||||
var data = {};
|
||||
var p = new BufferReader(raw);
|
||||
/**
|
||||
* Instantiate master key from serialized data.
|
||||
* @returns {MasterKey}
|
||||
*/
|
||||
|
||||
data.encrypted = p.readU8() === 1;
|
||||
MasterKey.fromRaw = function fromRaw(raw) {
|
||||
var p = new BufferReader(raw);
|
||||
var encrypted, phrase, passphrase, xprivkey, key;
|
||||
|
||||
encrypted = p.readU8() === 1;
|
||||
|
||||
if (p.readU8() === 1) {
|
||||
data.phrase = p.readVarBytes();
|
||||
data.passphrase = p.readVarBytes();
|
||||
phrase = p.readVarBytes();
|
||||
passphrase = p.readVarBytes();
|
||||
}
|
||||
|
||||
data.xprivkey = p.readBytes(82);
|
||||
xprivkey = p.readBytes(82);
|
||||
|
||||
if (!data.encrypted)
|
||||
data.key = bcoin.hd.fromRaw(data.xprivkey);
|
||||
if (!encrypted)
|
||||
key = bcoin.hd.fromRaw(xprivkey);
|
||||
|
||||
return new MasterKey(data);
|
||||
return new MasterKey({
|
||||
encrypted: encrypted,
|
||||
phrase: phrase,
|
||||
passphrase: passphrase,
|
||||
xprivkey: xprivkey,
|
||||
key: key
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiate master key from an HDPrivateKey.
|
||||
* @param {HDPrivateKey} key
|
||||
* @returns {MasterKey}
|
||||
*/
|
||||
|
||||
MasterKey.fromKey = function fromKey(key) {
|
||||
var data = {};
|
||||
|
||||
data.encrypted = false;
|
||||
var phrase, passphrase;
|
||||
|
||||
if (key.mnemonic) {
|
||||
data.phrase = new Buffer(key.mnemonic.phrase, 'utf8');
|
||||
data.passphrase = new Buffer(key.mnemonic.passphrase, 'utf8');
|
||||
phrase = new Buffer(key.mnemonic.phrase, 'utf8');
|
||||
passphrase = new Buffer(key.mnemonic.passphrase, 'utf8');
|
||||
}
|
||||
|
||||
data.xprivkey = key.toRaw();
|
||||
|
||||
data.key = key;
|
||||
|
||||
return new MasterKey(data);
|
||||
return new MasterKey({
|
||||
encrypted: false,
|
||||
phrase: phrase,
|
||||
passphrase: passphrase,
|
||||
xprivkey: key.toRaw(),
|
||||
key: key
|
||||
});
|
||||
};
|
||||
|
||||
MasterKey.prototype.toJSON = function toJSON() {
|
||||
var json = {};
|
||||
/**
|
||||
* Convert master key to a jsonifiable object.
|
||||
* @returns {Object}
|
||||
*/
|
||||
|
||||
json.encrypted = this.encrypted;
|
||||
MasterKey.prototype.toJSON = function toJSON() {
|
||||
var phrase, passphrase, xprivkey;
|
||||
|
||||
if (this.encrypted) {
|
||||
if (this.phrase) {
|
||||
json.phrase = this.phrase.toString('hex');
|
||||
json.passphrase = this.passphrase.toString('hex');
|
||||
phrase = this.phrase.toString('hex');
|
||||
passphrase = this.passphrase.toString('hex');
|
||||
}
|
||||
json.xprivkey = this.xprivkey.toString('hex');
|
||||
xprivkey = this.xprivkey.toString('hex');
|
||||
} else {
|
||||
if (this.phrase) {
|
||||
json.phrase = this.phrase.toString('utf8');
|
||||
json.passphrase = this.passphrase.toString('utf8');
|
||||
phrase = this.phrase.toString('utf8');
|
||||
passphrase = this.passphrase.toString('utf8');
|
||||
}
|
||||
json.xprivkey = utils.toBase58(this.xprivkey);
|
||||
xprivkey = utils.toBase58(this.xprivkey);
|
||||
}
|
||||
|
||||
return json;
|
||||
return {
|
||||
encrypted: this.encrypted,
|
||||
phrase: phrase,
|
||||
passphrase: passphrase,
|
||||
xprivkey: xprivkey
|
||||
};
|
||||
};
|
||||
|
||||
MasterKey.fromJSON = function fromJSON(json) {
|
||||
var data = {};
|
||||
/**
|
||||
* Instantiate master key from jsonified object.
|
||||
* @returns {MasterKey}
|
||||
*/
|
||||
|
||||
data.encrypted = json.encrypted;
|
||||
MasterKey.fromJSON = function fromJSON(json) {
|
||||
var phrase, passphrase, xprivkey, key;
|
||||
|
||||
if (json.encrypted) {
|
||||
if (json.phrase) {
|
||||
data.phrase = new Buffer(json.phrase, 'hex');
|
||||
data.passphrase = new Buffer(json.passphrase, 'hex');
|
||||
phrase = new Buffer(json.phrase, 'hex');
|
||||
passphrase = new Buffer(json.passphrase, 'hex');
|
||||
}
|
||||
data.xprivkey = new Buffer(json.xprivkey, 'hex');
|
||||
xprivkey = new Buffer(json.xprivkey, 'hex');
|
||||
} else {
|
||||
if (json.phrase) {
|
||||
data.phrase = new Buffer(json.phrase, 'utf8');
|
||||
data.passphrase = new Buffer(json.passphrase, 'utf8');
|
||||
phrase = new Buffer(json.phrase, 'utf8');
|
||||
passphrase = new Buffer(json.passphrase, 'utf8');
|
||||
}
|
||||
data.xprivkey = utils.fromBase58(json.xprivkey);
|
||||
xprivkey = utils.fromBase58(json.xprivkey);
|
||||
}
|
||||
|
||||
if (!data.encrypted)
|
||||
data.key = bcoin.hd.fromRaw(data.xprivkey);
|
||||
if (!json.encrypted)
|
||||
key = bcoin.hd.fromRaw(xprivkey);
|
||||
|
||||
return new MasterKey(data);
|
||||
return new MasterKey({
|
||||
encrypted: json.encrypted,
|
||||
phrase: phrase,
|
||||
passphrase: passphrase,
|
||||
xprivkey: xprivkey,
|
||||
key: key
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Test whether an object is a MasterKey.
|
||||
* @param {Object} obj
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
MasterKey.isMasterKey = function isMasterKey(obj) {
|
||||
return obj
|
||||
&& typeof obj.encrypted === 'boolean'
|
||||
|
||||
@ -505,6 +505,31 @@ WalletDB.prototype.getAccount = function getAccount(id, name, callback) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* List account names and indexes from the db.
|
||||
* @param {WalletID} id
|
||||
* @param {Function} callback - Returns [Error, Array].
|
||||
*/
|
||||
|
||||
WalletDB.prototype.getAccounts = function getAccounts(id, callback) {
|
||||
var accounts = [];
|
||||
this.db.iterate({
|
||||
gte: 'i/' + id + '/',
|
||||
lte: 'i/' + id + '/~',
|
||||
values: true,
|
||||
parse: function(value, key) {
|
||||
var name = key.split('/')[2];
|
||||
var index = value.readUInt32LE(0, true);
|
||||
accounts[index] = name;
|
||||
}
|
||||
}, function(err) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
return callback(null, accounts);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Lookup the corresponding account name's index.
|
||||
* @param {WalletID} id
|
||||
|
||||
@ -768,7 +768,11 @@ describe('Wallet', function() {
|
||||
w1.fill(t3, { rate: 10000, round: true }, function(err) {
|
||||
assert(err);
|
||||
assert.equal(err.requiredFunds, 25000);
|
||||
cb();
|
||||
w1.getAccounts(function(err, accounts) {
|
||||
assert.ifError(err);
|
||||
assert.deepEqual(accounts, ['default', 'foo']);
|
||||
cb();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -843,6 +847,43 @@ describe('Wallet', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should fill tx with inputs when encrypted', function(cb) {
|
||||
wdb.create({ passphrase: 'foo' }, function(err, w1) {
|
||||
assert.ifError(err);
|
||||
w1.master.destroy();
|
||||
|
||||
// Coinbase
|
||||
var t1 = bcoin.mtx()
|
||||
.addOutput(w1, 5460)
|
||||
.addOutput(w1, 5460)
|
||||
.addOutput(w1, 5460)
|
||||
.addOutput(w1, 5460);
|
||||
|
||||
t1.addInput(dummyInput);
|
||||
|
||||
wdb.addTX(t1, function(err) {
|
||||
assert.ifError(err);
|
||||
|
||||
// Create new transaction
|
||||
var t2 = bcoin.mtx().addOutput(w1, 5460);
|
||||
w1.fill(t2, { rate: 10000, round: true }, function(err) {
|
||||
assert.ifError(err);
|
||||
// Should fail
|
||||
w1.sign(t2, 'bar', function(err) {
|
||||
assert(err);
|
||||
assert(!t2.verify());
|
||||
// Should succeed
|
||||
w1.sign(t2, 'foo', function(err) {
|
||||
assert.ifError(err);
|
||||
assert(t2.verify());
|
||||
cb();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should cleanup', function(cb) {
|
||||
constants.tx.COINBASE_MATURITY = 100;
|
||||
cb();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user