wallet refactor.

This commit is contained in:
Christopher Jeffrey 2016-06-24 15:12:47 -07:00
parent 2a18a76973
commit f1376e5a99
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
3 changed files with 261 additions and 326 deletions

View File

@ -2809,25 +2809,14 @@ Script.prototype.isNulldata = function isNulldata(sloppy) {
return true;
}
if (this.raw.length === 2) {
if (this.raw[1] === opcodes.OP_0)
return true;
if (this.raw[1] >= opcodes.OP_1 && this.raw[1] <= opcodes.OP_16)
return true;
return false;
}
if (this.raw.length === 2)
return Script.getSmall(this.raw[1]) !== -1;
if (this.raw[1] >= 0x01 && this.raw[1] <= 0x4b) {
if (this.raw[1] + 2 !== this.raw.length)
return false;
return true;
}
if (this.raw[1] >= 0x01 && this.raw[1] <= 0x4b)
return this.raw[1] + 2 === this.raw.length;
if (this.raw[1] === opcodes.OP_PUSHDATA1) {
if (this.raw[2] + 3 !== this.raw.length)
return false;
return true;
}
if (this.raw[1] === opcodes.OP_PUSHDATA1)
return this.raw[2] > 75 && this.raw[2] + 3 === this.raw.length;
return false;
};

View File

@ -39,23 +39,37 @@ var BufferWriter = require('./writer');
* (default=account key "address").
*/
function Wallet(options) {
var master;
function Wallet(db, options) {
if (!(this instanceof Wallet))
return new Wallet(options);
return new Wallet(db, options);
EventEmitter.call(this);
assert(options, 'Options required.');
assert(options.db, 'DB required.');
assert(db, 'DB required.');
this.options = options;
this.network = bcoin.network.get(options.network);
this.db = options.db;
this.db = db;
this.network = db.network;
this.locker = new bcoin.locker(this);
master = options.master;
this.id = null;
this.master = null;
this.initialized = false;
this.accountDepth = 0;
this.account = null;
if (options)
this.fromOptions(options);
}
utils.inherits(Wallet, EventEmitter);
Wallet.fromOptions = function fromOptions(db, options) {
return new Wallet(db).fromOptions(options);
};
Wallet.prototype.fromOptions = function fromOptions(options) {
var master = options.master;
if (!master)
master = bcoin.hd.fromMnemonic(null, this.network);
@ -66,58 +80,47 @@ function Wallet(options) {
if (!MasterKey.isMasterKey(master))
master = MasterKey.fromKey(master);
this.id = options.id || null;
this.master = master;
this.initialized = options.initialized || false;
this.accountDepth = options.accountDepth || 0;
this.id = options.id || this.getID();
this.loaded = false;
this.loading = false;
this.account = null;
return this;
};
if (!this.id)
this.id = this.getID();
Wallet.prototype.init = function init(options, callback) {
var self = this;
if (this.options.passphrase)
this.master.encrypt(this.options.passphrase);
}
assert(!this.initialized);
this.initialized = true;
utils.inherits(Wallet, EventEmitter);
if (options.passphrase)
this.master.encrypt(options.passphrase);
/**
* Open the wallet, register with the database.
* @param {Function} callback
*/
this.createAccount(options, function(err, account) {
if (err)
return callback(err);
assert(account);
self.account = account;
return callback();
});
};
Wallet.prototype.open = function open(callback) {
var self = this;
assert(this.initialized);
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', e);
return callback(e);
}
this.init(function(err) {
if (err) {
self.emit('error', err);
this.getAccount(0, function(err, account) {
if (err)
return callback(err);
}
self.loading = false;
self.loaded = true;
self.emit('open');
if (!account)
return callback(new Error('Default account not found.'));
self.account = account;
return callback();
});
@ -129,15 +132,9 @@ Wallet.prototype.open = function open(callback) {
* @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);
this.master.destroy();
try {
@ -147,8 +144,6 @@ Wallet.prototype.destroy = function destroy(callback) {
return callback(e);
}
this.loaded = false;
return utils.nextTick(callback);
};
@ -160,33 +155,6 @@ Wallet.prototype.destroy = function destroy(callback) {
* @param {Function} callback
*/
Wallet.prototype.init = function init(callback) {
var self = this;
function done(err, account) {
if (err)
return callback(err);
if (!account)
return callback(new Error('Account not found.'));
self.account = account;
return callback();
}
this.db.open(function(err) {
if (err)
return callback(err);
if (self.initialized)
return self.getAccount(0, done);
self.initialized = true;
self.createAccount(self.options, done);
});
};
/**
* Add a public account key to the wallet (multisig).
* Saves the key in the wallet database.
@ -686,6 +654,34 @@ Wallet.prototype.deriveInputs = function deriveInputs(tx, callback) {
});
};
Wallet.prototype.getKeyring = function getKeyring(hash, callback) {
var self = this;
var address;
if (!hash)
return callback();
this.getPath(hash, function(err, path) {
if (err)
return callback(err);
if (!path)
return callback();
self.getAccount(path.account, function(err, account) {
if (err)
return callback(err);
if (!account)
return callback();
address = account.deriveAddress(path.change, path.index);
return callback(null, address);
});
});
};
/**
* Map input addresses to paths.
* @param {TX|Input} tx
@ -1419,14 +1415,13 @@ Wallet.prototype.toJSON = function toJSON() {
* @throws Error on bad decrypt
*/
Wallet.parseJSON = function parseJSON(json) {
return {
network: json.network,
id: json.id,
initialized: json.initialized,
accountDepth: json.accountDepth,
master: MasterKey.fromJSON(json.master)
};
Wallet.prototype.fromJSON = function fromJSON(json) {
this.network = bcoin.network.get(json.network);
this.id = json.id;
this.initialized = json.initialized;
this.accountDepth = json.accountDepth;
this.master = MasterKey.fromJSON(json.master);
return this;
};
/**
@ -1457,21 +1452,14 @@ Wallet.prototype.toRaw = function toRaw(writer) {
* @returns {Object}
*/
Wallet.parseRaw = function parseRaw(data) {
Wallet.prototype.fromRaw = function fromRaw(data) {
var p = new BufferReader(data);
var network = bcoin.network.fromMagic(p.readU32());
var id = p.readVarString('utf8');
var initialized = p.readU8() === 1;
var accountDepth = p.readU32();
var master = MasterKey.fromRaw(p.readVarBytes());
return {
network: network.type,
id: id,
initialized: initialized,
accountDepth: accountDepth,
master: master
};
this.network = bcoin.network.fromMagic(p.readU32());
this.id = p.readVarString('utf8');
this.initialized = p.readU8() === 1;
this.accountDepth = p.readU32();
this.master = MasterKey.fromRaw(p.readVarBytes());
return this;
};
/**
@ -1480,8 +1468,8 @@ Wallet.parseRaw = function parseRaw(data) {
* @returns {Wallet}
*/
Wallet.fromRaw = function fromRaw(data) {
return new Wallet(Wallet.parseRaw(data));
Wallet.fromRaw = function fromRaw(db, data) {
return new Wallet(db).fromRaw(data);
};
/**
@ -1491,8 +1479,8 @@ Wallet.fromRaw = function fromRaw(data) {
* @returns {Wallet}
*/
Wallet.fromJSON = function fromJSON(json) {
return new Wallet(Wallet.parseJSON(json));
Wallet.fromJSON = function fromJSON(db, json) {
return new Wallet(db).fromJSON(json);
};
/**
@ -1531,28 +1519,60 @@ Wallet.isWallet = function isWallet(obj) {
* @param {String?} options.name - Account name
*/
function Account(options) {
function Account(db, options) {
var i;
if (!(this instanceof Account))
return new Account(options);
return new Account(db, options);
EventEmitter.call(this);
assert(db, 'Database is required.');
this.db = db;
this.network = db.network;
this.lookahead = Account.LOOKAHEAD;
this.cache = new bcoin.lru(20, 1);
this.loaded = false;
this.loading = false;
this.receiveAddress = null;
this.changeAddress = null;
this.id = null;
this.name = null;
this.witness = false;
this.accountKey = null;
this.accountIndex = 0;
this.receiveDepth = 0;
this.changeDepth = 0;
this.type = null;
this.keys = [];
this.m = 1;
this.n = 1;
this.initialized = false;
if (options)
this.fromOptions(options);
}
utils.inherits(Account, EventEmitter);
Account.fromOptions = function fromOptions(db, options) {
return new Account(db).fromOptions(options);
};
Account.prototype.fromOptions = function fromOptions(options) {
assert(options, 'Options are required.');
assert(options.db, 'Database is required.');
assert(options.id, 'Wallet ID is required.');
assert(options.accountKey, 'Account key is required.');
assert(utils.isNumber(options.accountIndex), 'Account index is required.');
this.options = options;
this.network = bcoin.network.get(options.network);
this.db = options.db;
this.lookahead = Account.LOOKAHEAD;
this.id = options.id;
this.name = options.name;
this.witness = options.witness || false;
this.witness = options.witness != null
? options.witness
: this.network.witness;
this.accountKey = options.accountKey;
this.accountIndex = options.accountIndex;
this.receiveDepth = options.receiveDepth || 0;
@ -1563,13 +1583,6 @@ function Account(options) {
this.n = options.n || 1;
this.initialized = options.initialized || false;
this.loaded = false;
this.loading = false;
this.receiveAddress = null;
this.changeAddress = null;
this.cache = new bcoin.lru(20, 1);
if (this.n > 1)
this.type = 'multisig';
@ -1588,9 +1601,9 @@ function Account(options) {
for (i = 0; i < options.keys.length; i++)
this.pushKey(options.keys[i]);
}
}
utils.inherits(Account, EventEmitter);
return this;
};
/*
* Default address lookahead.
@ -1599,58 +1612,6 @@ utils.inherits(Account, EventEmitter);
Account.LOOKAHEAD = 5;
/**
* Open the account, register with the database.
* @param {Function} callback
*/
Account.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;
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 account, unregister with the database.
* @method
* @param {Function} callback
*/
Account.prototype.close =
Account.prototype.destroy = function destroy(callback) {
callback = utils.ensure(callback);
if (!this.loaded)
return utils.nextTick(callback);
assert(!this.loading);
this.loaded = false;
return utils.nextTick(callback);
};
/**
* Attempt to intialize the account (generating
* the first addresses along with the lookahead
@ -1666,12 +1627,6 @@ Account.prototype.init = function init(callback) {
return this.save(callback);
}
if (this.initialized) {
this.receiveAddress = this.deriveReceive(this.receiveDepth - 1);
this.changeAddress = this.deriveChange(this.changeDepth - 1);
return callback();
}
this.initialized = true;
assert(this.receiveDepth === 0);
@ -1680,6 +1635,16 @@ Account.prototype.init = function init(callback) {
this.setDepth(1, 1, callback);
};
Account.prototype.open = function open(callback) {
if (!this.initialized)
return callback();
this.receiveAddress = this.deriveReceive(this.receiveDepth - 1);
this.changeAddress = this.deriveChange(this.changeDepth - 1);
return callback();
};
/**
* Add a public account key to the account (multisig).
* Does not update the database.
@ -1809,6 +1774,7 @@ Account.prototype.addKey = function addKey(key, callback) {
error = e;
}
// Try to initialize again.
this.init(function(err) {
if (err)
return callback(err);
@ -2186,24 +2152,27 @@ Account.prototype.toJSON = function toJSON() {
* @throws Error on bad decrypt
*/
Account.parseJSON = function parseJSON(json) {
return {
network: json.network,
id: json.id,
name: json.name,
initialized: json.initialized,
type: json.type,
m: json.m,
n: json.n,
witness: json.witness,
accountIndex: json.accountIndex,
receiveDepth: json.receiveDepth,
changeDepth: json.changeDepth,
accountKey: bcoin.hd.fromBase58(json.accountKey),
keys: json.keys.map(function(key) {
return bcoin.hd.fromBase58(key);
})
};
Account.prototype.fromJSON = function fromJSON(json) {
var i;
this.network = bcoin.network.get(json.network);
this.id = json.id;
this.name = json.name;
this.initialized = json.initialized;
this.type = json.type;
this.m = json.m;
this.n = json.n;
this.witness = json.witness;
this.accountIndex = json.accountIndex;
this.receiveDepth = json.receiveDepth;
this.changeDepth = json.changeDepth;
this.accountKey = bcoin.hd.fromBase58(json.accountKey);
this.keys = [];
for (i = 0; i < json.keys.length; i++)
this.keys.push(bcoin.hd.fromBase58(key));
return this;
};
/**
@ -2246,42 +2215,30 @@ Account.prototype.toRaw = function toRaw(writer) {
* @returns {Object}
*/
Account.parseRaw = function parseRaw(data) {
Account.prototype.fromRaw = function fromRaw(data) {
var p = new BufferReader(data);
var network = bcoin.network.fromMagic(p.readU32());
var id = p.readVarString('utf8');
var name = p.readVarString('utf8');
var initialized = p.readU8() === 1;
var type = p.readU8() === 0 ? 'pubkeyhash' : 'multisig';
var m = p.readU8();
var n = p.readU8();
var witness = p.readU8() === 1;
var accountIndex = p.readU32();
var receiveDepth = p.readU32();
var changeDepth = p.readU32();
var accountKey = bcoin.hd.fromRaw(p.readBytes(82));
var count = p.readU8();
var keys = [];
var i;
var i, count;
this.network = bcoin.network.fromMagic(p.readU32());
this.id = p.readVarString('utf8');
this.name = p.readVarString('utf8');
this.initialized = p.readU8() === 1;
this.type = p.readU8() === 0 ? 'pubkeyhash' : 'multisig';
this.m = p.readU8();
this.n = p.readU8();
this.witness = p.readU8() === 1;
this.accountIndex = p.readU32();
this.receiveDepth = p.readU32();
this.changeDepth = p.readU32();
this.accountKey = bcoin.hd.fromRaw(p.readBytes(82));
this.keys = [];
count = p.readU8();
for (i = 0; i < count; i++)
keys.push(bcoin.hd.fromRaw(p.readBytes(82)));
this.keys.push(bcoin.hd.fromRaw(p.readBytes(82)));
return {
network: network.type,
id: id,
name: name,
initialized: initialized,
type: type,
m: m,
n: n,
witness: witness,
accountIndex: accountIndex,
receiveDepth: receiveDepth,
changeDepth: changeDepth,
accountKey: accountKey,
keys: keys
};
return this;
};
/**
@ -2290,8 +2247,8 @@ Account.parseRaw = function parseRaw(data) {
* @returns {Account}
*/
Account.fromRaw = function fromRaw(data) {
return new Account(Account.parseRaw(data));
Account.fromRaw = function fromRaw(db, data) {
return new Account(db).fromRaw(data);
};
/**
@ -2301,8 +2258,8 @@ Account.fromRaw = function fromRaw(data) {
* @returns {Account}
*/
Account.fromJSON = function fromJSON(json) {
return new Account(Account.parseJSON(json));
Account.fromJSON = function fromJSON(db, json) {
return new Account(db).fromJSON(json);
};
/**

View File

@ -45,7 +45,7 @@ function WalletDB(options) {
EventEmitter.call(this);
this.watchers = [];
this.watchers = {};
this.options = options;
this.loaded = false;
this.network = bcoin.network.get(options.network);
@ -249,19 +249,18 @@ WalletDB.prototype.destroy = function destroy(callback) {
};
/**
* Register a wallet with the walletdb.
* @param {WalletID} id
* @param {Wallet} wallet
* Register an object with the walletdb.
* @param {Object} object
*/
WalletDB.prototype.register = function register(wallet) {
var id = wallet.id;
WalletDB.prototype.register = function register(object) {
var id = object.id;
if (!this.watchers[id])
this.watchers[id] = { wallet: wallet, refs: 0 };
this.watchers[id] = { object: object, refs: 0 };
// Should never happen, and if it does, I will cry.
assert(this.watchers[id].wallet === wallet, 'I\'m crying.');
assert(this.watchers[id].object === object, 'I\'m crying.');
// We do some reference counting here
// because we're thug like that (police
@ -270,27 +269,41 @@ WalletDB.prototype.register = function register(wallet) {
};
/**
* Unregister a wallet with the walletdb.
* @param {WalletID} id
* @param {Wallet} wallet
* Unregister a object with the walletdb.
* @param {Object} object
*/
WalletDB.prototype.unregister = function unregister(wallet) {
var id = wallet.id;
WalletDB.prototype.unregister = function unregister(object) {
var id = object.id;
var watcher = this.watchers[id];
if (!watcher)
return;
assert(watcher.wallet === wallet);
assert(watcher.refs !== 0, '`wallet.destroy()` called twice!');
assert(watcher.object === object);
assert(watcher.refs !== 0, '`destroy()` called twice!');
if (--watcher.refs === 0)
delete this.watchers[id];
};
/**
* Fire an event for a registered wallet.
* Watch an object (increment reference count).
* @param {Object} object
*/
WalletDB.prototype.watch = function watch(object) {
var id = object.id;
var watcher = this.watchers[id];
if (!watcher)
return;
watcher.refs++;
};
/**
* Fire an event for a registered object.
* @param {WalletID} id
* @param {...Object} args
*/
@ -307,11 +320,11 @@ WalletDB.prototype.fire = function fire(id) {
for (i = 1; i < arguments.length; i++)
args[i - 1] = arguments[i];
watcher.wallet.emit.apply(watcher.wallet, args);
watcher.object.emit.apply(watcher.object, args);
};
/**
* Test for a listener on a registered wallet.
* Test for a listener on a registered object.
* @param {WalletID} id
* @param {String} event
* @returns {Boolean}
@ -323,7 +336,7 @@ WalletDB.prototype.hasListener = function hasListener(id, event) {
if (!watcher)
return false;
if (watcher.wallet.listeners(event).length !== 0)
if (watcher.object.listeners(event).length !== 0)
return true;
return false;
@ -337,14 +350,16 @@ WalletDB.prototype.hasListener = function hasListener(id, event) {
WalletDB.prototype.get = function get(id, callback) {
var self = this;
var wallet;
var watcher, wallet;
if (!id)
return callback();
if (this.watchers[id]) {
this.watchers[id].refs++;
return callback(null, this.watchers[id].wallet);
watcher = this.watchers[id];
if (watcher) {
watcher.refs++;
return callback(null, watcher.object);
}
this.db.get('w/' + id, function(err, data) {
@ -355,9 +370,13 @@ WalletDB.prototype.get = function get(id, callback) {
return callback();
try {
data = bcoin.wallet.parseRaw(data);
data.db = self;
wallet = new bcoin.wallet(data);
wallet = bcoin.wallet.fromRaw(self, data);
} catch (e) {
return callback(e);
}
try {
self.register(wallet);
} catch (e) {
return callback(e);
}
@ -371,33 +390,6 @@ WalletDB.prototype.get = function get(id, callback) {
});
};
/**
* Get raw wallet from the database, do not instantiate.
* @param {WalletID} id
* @param {Function} callback - Returns [Error, {@link Wallet}].
*/
WalletDB.prototype.getRaw = function getRaw(id, callback) {
if (!id)
return callback();
this.db.get('w/' + id, function(err, data) {
if (err && err.type !== 'NotFoundError')
return callback();
if (!data)
return callback();
try {
data = bcoin.wallet.parseRaw(data);
} catch (e) {
return callback(e);
}
return callback(null, data);
});
};
/**
* Save a wallet to the database.
* @param {Wallet} wallet
@ -436,13 +428,19 @@ WalletDB.prototype.create = function create(options, callback) {
if (exists)
return callback(new Error('Wallet already exists.'));
options = utils.merge({}, options);
try {
wallet = bcoin.wallet.fromOptions(self, options);
} catch (e) {
return callback(e);
}
options.network = self.network;
options.db = self;
wallet = new bcoin.wallet(options);
try {
self.register(wallet);
} catch (e) {
return callback(e);
}
wallet.open(function(err) {
wallet.init(options, function(err) {
if (err)
return callback(err);
@ -510,9 +508,7 @@ WalletDB.prototype.getAccount = function getAccount(id, name, callback) {
return callback();
try {
data = bcoin.account.parseRaw(data);
data.db = self;
account = new bcoin.account(data);
account = bcoin.account.fromRaw(self, data);
} catch (e) {
return callback(e);
}
@ -620,16 +616,9 @@ WalletDB.prototype.createAccount = function createAccount(options, callback) {
if (exists)
return callback(new Error('Account already exists.'));
options = utils.merge({}, options);
account = bcoin.account.fromOptions(self, options);
if (self.network.witness)
options.witness = options.witness !== false;
options.network = self.network;
options.db = self;
account = new bcoin.account(options);
account.open(function(err) {
account.init(function(err) {
if (err)
return callback(err);