wallet: more rewriting.

This commit is contained in:
Christopher Jeffrey 2016-10-01 12:31:43 -07:00
parent 2544e5310a
commit 61a77d90e9
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
11 changed files with 706 additions and 359 deletions

View File

@ -1238,7 +1238,7 @@ ChainDB.prototype.getCoinsByAddress = co(function* getCoinsByAddress(addresses)
if (!hash) if (!hash)
continue; continue;
keys = yield this.db.iterate({ keys = yield this.db.keys({
gte: layout.C(hash, constants.ZERO_HASH, 0), gte: layout.C(hash, constants.ZERO_HASH, 0),
lte: layout.C(hash, constants.MAX_HASH, 0xffffffff), lte: layout.C(hash, constants.MAX_HASH, 0xffffffff),
parse: layout.Cc parse: layout.Cc
@ -1263,11 +1263,9 @@ ChainDB.prototype.getCoinsByAddress = co(function* getCoinsByAddress(addresses)
ChainDB.prototype.getEntries = function getEntries() { ChainDB.prototype.getEntries = function getEntries() {
var self = this; var self = this;
return this.db.iterate({ return this.db.values({
gte: layout.e(constants.ZERO_HASH), gte: layout.e(constants.ZERO_HASH),
lte: layout.e(constants.MAX_HASH), lte: layout.e(constants.MAX_HASH),
keys: false,
values: true,
parse: function(key, value) { parse: function(key, value) {
return bcoin.chainentry.fromRaw(self.chain, value); return bcoin.chainentry.fromRaw(self.chain, value);
} }
@ -1294,7 +1292,7 @@ ChainDB.prototype.getHashesByAddress = co(function* getHashesByAddress(addresses
if (!hash) if (!hash)
continue; continue;
yield this.db.iterate({ yield this.db.keys({
gte: layout.T(hash, constants.ZERO_HASH), gte: layout.T(hash, constants.ZERO_HASH),
lte: layout.T(hash, constants.MAX_HASH), lte: layout.T(hash, constants.MAX_HASH),
parse: function(key) { parse: function(key) {

View File

@ -301,14 +301,17 @@ LowlevelUp.prototype.has = co(function* has(key) {
* @returns {Promise} - Returns Array. * @returns {Promise} - Returns Array.
*/ */
LowlevelUp.prototype.iterate = co(function* iterate(options) { LowlevelUp.prototype.range = co(function* range(options) {
var items = []; var items = [];
var parse = options.parse; var parse = options.parse;
var iter, item, data; var iter, item;
assert(typeof parse === 'function', 'Parse must be a function.'); iter = this.iterator({
gte: options.gte,
iter = this.iterator(options); lte: options.lte,
keys: true,
values: true
});
for (;;) { for (;;) {
item = yield iter.next(); item = yield iter.next();
@ -316,20 +319,130 @@ LowlevelUp.prototype.iterate = co(function* iterate(options) {
if (!item) if (!item)
break; break;
try { if (parse) {
data = parse(item.key, item.value); try {
} catch (e) { item = parse(item.key, item.value);
yield iter.end(); } catch (e) {
throw e; yield iter.end();
throw e;
}
} }
if (data) if (item)
items.push(data); items.push(item);
} }
return items; return items;
}); });
/**
* Collect all keys from iterator options.
* @param {Object} options - Iterator options.
* @returns {Promise} - Returns Array.
*/
LowlevelUp.prototype.keys = co(function* keys(options) {
var keys = [];
var parse = options.parse;
var iter, item, key;
iter = this.iterator({
gte: options.gte,
lte: options.lte,
keys: true,
values: false
});
for (;;) {
item = yield iter.next();
if (!item)
break;
key = item.key;
if (parse) {
try {
key = parse(key);
} catch (e) {
yield iter.end();
throw e;
}
}
if (key)
keys.push(key);
}
return keys;
});
/**
* Collect all keys from iterator options.
* @param {Object} options - Iterator options.
* @returns {Promise} - Returns Array.
*/
LowlevelUp.prototype.values = co(function* values(options) {
var values = [];
var parse = options.parse;
var iter, item, value;
iter = this.iterator({
gte: options.gte,
lte: options.lte,
keys: false,
values: true
});
for (;;) {
item = yield iter.next();
if (!item)
break;
value = item.value;
if (parse) {
try {
value = parse(value);
} catch (e) {
yield iter.end();
throw e;
}
}
if (value)
values.push(value);
}
return values;
});
/**
* Dump database (for debugging).
* @returns {Promise} - Returns Object.
*/
LowlevelUp.prototype.dump = co(function* dump() {
var records = {};
var i, items, item, key, value;
items = yield this.range({
gte: new Buffer([0x00]),
lte: new Buffer([0xff])
});
for (i = 0; i < items.length; i++) {
item = items[i];
key = item.key.toString('hex');
value = item.value.toString('hex');
records[key] = value;
}
return records;
});
/** /**
* Write and assert a version number for the database. * Write and assert a version number for the database.
* @param {Number} version * @param {Number} version

View File

@ -16,6 +16,8 @@ var networks = bcoin.networks;
var BufferReader = require('../utils/reader'); var BufferReader = require('../utils/reader');
var BufferWriter = require('../utils/writer'); var BufferWriter = require('../utils/writer');
var scriptTypes = constants.scriptTypes; var scriptTypes = constants.scriptTypes;
var Address = require('./address');
var ec = require('../crypto/ec');
/** /**
* Represents a key ring which amounts to an address. * Represents a key ring which amounts to an address.
@ -61,6 +63,9 @@ function KeyRing(options, network) {
KeyRing.prototype.fromOptions = function fromOptions(options, network) { KeyRing.prototype.fromOptions = function fromOptions(options, network) {
var key = toKey(options); var key = toKey(options);
var script = options.script;
var compressed = options.compressed;
var network = options.network;
if (Buffer.isBuffer(key)) if (Buffer.isBuffer(key))
return this.fromKey(key, network); return this.fromKey(key, network);
@ -73,18 +78,15 @@ KeyRing.prototype.fromOptions = function fromOptions(options, network) {
if (options.privateKey) if (options.privateKey)
key = toKey(options.privateKey); key = toKey(options.privateKey);
if (options.network)
this.network = bcoin.network.get(options.network);
if (options.witness != null) { if (options.witness != null) {
assert(typeof options.witness === 'boolean'); assert(typeof options.witness === 'boolean');
this.witness = options.witness; this.witness = options.witness;
} }
if (options.script) if (script)
return this.fromScript(key, options.script, this.network); return this.fromScript(key, script, compressed, network);
this.fromKey(key, this.network); this.fromKey(key, compressed, network);
}; };
/** /**
@ -100,44 +102,51 @@ KeyRing.fromOptions = function fromOptions(options) {
/** /**
* Inject data from private key. * Inject data from private key.
* @private * @private
* @param {Buffer} privateKey * @param {Buffer} key
* @param {Boolean?} compressed * @param {Boolean?} compressed
* @param {(NetworkType|Network}) network * @param {(NetworkType|Network}) network
*/ */
KeyRing.prototype.fromPrivate = function fromPrivate(privateKey, network) { KeyRing.prototype.fromPrivate = function fromPrivate(key, compressed, network) {
assert(Buffer.isBuffer(privateKey), 'Private key must be a buffer.'); assert(Buffer.isBuffer(key), 'Private key must be a buffer.');
assert(bcoin.ec.privateKeyVerify(privateKey), 'Not a valid private key.'); assert(ec.privateKeyVerify(key), 'Not a valid private key.');
if (typeof compressed !== 'boolean') {
network = compressed;
compressed = null;
}
this.network = bcoin.network.get(network); this.network = bcoin.network.get(network);
this.privateKey = privateKey; this.privateKey = key;
this.publicKey = bcoin.ec.publicKeyCreate(this.privateKey, true); this.publicKey = ec.publicKeyCreate(key, compressed !== false);
return this; return this;
}; };
/** /**
* Instantiate keyring from a private key. * Instantiate keyring from a private key.
* @param {Buffer} privateKey * @param {Buffer} key
* @param {Boolean?} compressed * @param {Boolean?} compressed
* @param {(NetworkType|Network}) network * @param {(NetworkType|Network}) network
* @returns {KeyRing} * @returns {KeyRing}
*/ */
KeyRing.fromPrivate = function fromPrivate(privateKey, network) { KeyRing.fromPrivate = function fromPrivate(key, compressed, network) {
return new KeyRing().fromPrivate(privateKey, network); return new KeyRing().fromPrivate(key, compressed, network);
}; };
/** /**
* Inject data from public key. * Inject data from public key.
* @private * @private
* @param {Buffer} privateKey * @param {Buffer} key
* @param {(NetworkType|Network}) network * @param {(NetworkType|Network}) network
*/ */
KeyRing.prototype.fromPublic = function fromPublic(publicKey, network) { KeyRing.prototype.fromPublic = function fromPublic(key, network) {
assert(Buffer.isBuffer(publicKey), 'Public key must be a buffer.'); assert(Buffer.isBuffer(key), 'Public key must be a buffer.');
assert(bcoin.ec.publicKeyVerify(publicKey), 'Not a valid public key.'); assert(ec.publicKeyVerify(key), 'Not a valid public key.');
this.network = bcoin.network.get(network); this.network = bcoin.network.get(network);
this.publicKey = publicKey; this.publicKey = key;
return this; return this;
}; };
@ -147,12 +156,17 @@ KeyRing.prototype.fromPublic = function fromPublic(publicKey, network) {
* @returns {KeyRing} * @returns {KeyRing}
*/ */
KeyRing.generate = function(network) { KeyRing.generate = function(compressed, network) {
var key = new KeyRing(); var key;
key.network = bcoin.network.get(network);
key.privateKey = bcoin.ec.generatePrivateKey(); if (typeof compressed !== 'boolean') {
key.publicKey = bcoin.ec.publicKeyCreate(key.privateKey, true); network = compressed;
return key; compressed = null;
}
key = ec.generatePrivateKey();
return KeyRing.fromKey(key, compressed, network);
}; };
/** /**
@ -162,8 +176,8 @@ KeyRing.generate = function(network) {
* @returns {KeyRing} * @returns {KeyRing}
*/ */
KeyRing.fromPublic = function fromPublic(publicKey, network) { KeyRing.fromPublic = function fromPublic(key, network) {
return new KeyRing().fromPublic(publicKey, network); return new KeyRing().fromPublic(key, network);
}; };
/** /**
@ -173,14 +187,18 @@ KeyRing.fromPublic = function fromPublic(publicKey, network) {
* @param {(NetworkType|Network}) network * @param {(NetworkType|Network}) network
*/ */
KeyRing.prototype.fromKey = function fromKey(key, network) { KeyRing.prototype.fromKey = function fromKey(key, compressed, network) {
assert(Buffer.isBuffer(key), 'Key must be a buffer.'); assert(Buffer.isBuffer(key), 'Key must be a buffer.');
assert(key.length === 32 || key.length === 33, 'Not a key.');
if (key.length === 33) if (typeof compressed !== 'boolean') {
return this.fromPublic(key, network); network = compressed;
compressed = null;
}
return this.fromPrivate(key, network); if (key.length === 32)
return this.fromPrivate(key, compressed !== false, network);
return this.fromPublic(key, network);
}; };
/** /**
@ -190,8 +208,8 @@ KeyRing.prototype.fromKey = function fromKey(key, network) {
* @returns {KeyRing} * @returns {KeyRing}
*/ */
KeyRing.fromKey = function fromKey(key, network) { KeyRing.fromKey = function fromKey(key, compressed, network) {
return new KeyRing().fromKey(key, network); return new KeyRing().fromKey(key, compressed, network);
}; };
/** /**
@ -202,10 +220,17 @@ KeyRing.fromKey = function fromKey(key, network) {
* @param {(NetworkType|Network}) network * @param {(NetworkType|Network}) network
*/ */
KeyRing.prototype.fromScript = function fromScript(key, script, network) { KeyRing.prototype.fromScript = function fromScript(key, script, compressed, network) {
assert(script instanceof bcoin.script, 'Non-script passed into KeyRing.'); assert(script instanceof bcoin.script, 'Non-script passed into KeyRing.');
this.fromKey(key, network);
if (typeof compressed !== 'boolean') {
network = compressed;
compressed = null;
}
this.fromKey(key, compressed, network);
this.script = script; this.script = script;
return this; return this;
}; };
@ -217,8 +242,8 @@ KeyRing.prototype.fromScript = function fromScript(key, script, network) {
* @returns {KeyRing} * @returns {KeyRing}
*/ */
KeyRing.fromScript = function fromScript(key, script, network) { KeyRing.fromScript = function fromScript(key, script, compressed, network) {
return new KeyRing().fromScript(key, script, network); return new KeyRing().fromScript(key, script, compressed, network);
}; };
/** /**
@ -235,7 +260,8 @@ KeyRing.prototype.toSecret = function toSecret() {
p.writeU8(this.network.keyPrefix.privkey); p.writeU8(this.network.keyPrefix.privkey);
p.writeBytes(this.privateKey); p.writeBytes(this.privateKey);
p.writeU8(1); if (this.publicKey.length === 33)
p.writeU8(1);
p.writeChecksum(); p.writeChecksum();
@ -274,9 +300,7 @@ KeyRing.prototype.fromSecret = function fromSecret(data) {
p.verifyChecksum(); p.verifyChecksum();
assert(compressed === false, 'Cannot handle uncompressed.'); return this.fromPrivate(key, compressed, type);
return this.fromPrivate(key, type);
}; };
/** /**
@ -528,7 +552,7 @@ KeyRing.prototype.getKeyAddress = function getKeyAddress(enc) {
*/ */
KeyRing.prototype.compile = function compile(hash, type, version) { KeyRing.prototype.compile = function compile(hash, type, version) {
return bcoin.address.fromHash(hash, type, version, this.network); return Address.fromHash(hash, type, version, this.network);
}; };
/** /**
@ -651,7 +675,7 @@ KeyRing.prototype.getRedeem = function(hash) {
KeyRing.prototype.sign = function sign(msg) { KeyRing.prototype.sign = function sign(msg) {
assert(this.privateKey, 'Cannot sign without private key.'); assert(this.privateKey, 'Cannot sign without private key.');
return bcoin.ec.sign(msg, this.privateKey); return ec.sign(msg, this.privateKey);
}; };
/** /**
@ -662,7 +686,7 @@ KeyRing.prototype.sign = function sign(msg) {
*/ */
KeyRing.prototype.verify = function verify(msg, sig) { KeyRing.prototype.verify = function verify(msg, sig) {
return bcoin.ec.verify(msg, sig, this.publicKey); return ec.verify(msg, sig, this.publicKey);
}; };
/** /**
@ -680,6 +704,22 @@ KeyRing.prototype.getType = function getType() {
return scriptTypes.PUBKEYHASH; return scriptTypes.PUBKEYHASH;
}; };
/**
* Get address type.
* @returns {ScriptType}
*/
KeyRing.prototype.getAddressType = function getAddressType() {
if (this.witness) {
if (this.script)
return scriptTypes.WITNESSSCRIPTHASH;
return scriptTypes.WITNESSPUBKEYHASH;
}
if (this.script)
return scriptTypes.SCRIPTHASH;
return scriptTypes.PUBKEYHASH;
};
/* /*
* Getters * Getters
*/ */
@ -807,10 +847,12 @@ KeyRing.prototype.toRaw = function toRaw(writer) {
p.writeU8(this.witness ? 1 : 0); p.writeU8(this.witness ? 1 : 0);
if (this.privateKey) if (this.privateKey) {
p.writeVarBytes(this.privateKey); p.writeVarBytes(this.privateKey);
else p.writeU8(this.publicKey.length === 33);
} else {
p.writeVarBytes(this.publicKey); p.writeVarBytes(this.publicKey);
}
if (this.script) if (this.script)
p.writeVarBytes(this.script.toRaw()); p.writeVarBytes(this.script.toRaw());
@ -831,7 +873,7 @@ KeyRing.prototype.toRaw = function toRaw(writer) {
KeyRing.prototype.fromRaw = function fromRaw(data, network) { KeyRing.prototype.fromRaw = function fromRaw(data, network) {
var p = new BufferReader(data); var p = new BufferReader(data);
var key, script; var compressed, key, script;
this.network = bcoin.network.get(network); this.network = bcoin.network.get(network);
this.witness = p.readU8() === 1; this.witness = p.readU8() === 1;
@ -839,10 +881,12 @@ KeyRing.prototype.fromRaw = function fromRaw(data, network) {
key = p.readVarBytes(); key = p.readVarBytes();
if (key.length === 32) { if (key.length === 32) {
compressed = p.readU8() === 1;
this.privateKey = key; this.privateKey = key;
this.publicKey = bcoin.ec.publicKeyCreate(key, true); this.publicKey = ec.publicKeyCreate(key, compressed);
} else { } else {
this.publicKey = key; this.publicKey = key;
assert(ec.publicKeyVerify(key), 'Invalid public key.');
} }
script = p.readVarBytes(); script = p.readVarBytes();

View File

@ -295,6 +295,21 @@ LRU.prototype.keys = function keys() {
return keys; return keys;
}; };
/**
* Collect all values in the cache, sorted by LRU.
* @returns {String[]}
*/
LRU.prototype.values = function values() {
var values = [];
var item;
for (item = this.head; item; item = item.next)
values.push(item.value);
return values;
};
/** /**
* Convert the LRU cache to an array of items. * Convert the LRU cache to an array of items.
* @returns {Object[]} * @returns {Object[]}
@ -336,12 +351,16 @@ function NullCache(size) {}
NullCache.prototype.set = function set(key, value) {}; NullCache.prototype.set = function set(key, value) {};
NullCache.prototype.remove = function remove(key) {}; NullCache.prototype.remove = function remove(key) {};
NullCache.prototype.get = function get(key) {}; NullCache.prototype.get = function get(key) {};
NullCache.prototype.has = function has(key) {}; NullCache.prototype.has = function has(key) { return false; };
NullCache.prototype.reset = function reset() {}; NullCache.prototype.reset = function reset() {};
NullCache.prototype.keys = function keys(key) { return []; };
NullCache.prototype.values = function values(key) { return []; };
NullCache.prototype.toArray = function toArray(key) { return []; };
/* /*
* Expose * Expose
*/ */
LRU.nil = NullCache; LRU.nil = NullCache;
module.exports = LRU; module.exports = LRU;

View File

@ -349,12 +349,7 @@ Account.prototype._checkKeys = co(function* _checkKeys() {
ring = this.deriveReceive(0); ring = this.deriveReceive(0);
hash = ring.getScriptHash('hex'); hash = ring.getScriptHash('hex');
paths = yield this.db.getAddressPaths(hash); return yield this.db.hasAddress(this.wid, hash);
if (!paths)
return false;
return paths[this.wid] != null;
}); });
/** /**

View File

@ -17,6 +17,12 @@ layout.walletdb = {
pp: function(key) { pp: function(key) {
return key.slice(1); return key.slice(1);
}, },
P: function(wid, hash) {
return 'p' + pad32(wid) + hash;
},
Pp: function(key) {
return key.slice(11);
},
w: function(wid) { w: function(wid) {
return 'w' + pad32(wid); return 'w' + pad32(wid);
}, },

View File

@ -30,8 +30,10 @@ function Path() {
if (!(this instanceof Path)) if (!(this instanceof Path))
return new Path(); return new Path();
// Passed in by caller.
this.wid = null; this.wid = null;
this.name = null; // Passed in by caller. this.name = null;
this.account = 0; this.account = 0;
this.change = -1; this.change = -1;
this.index = -1; this.index = -1;
@ -97,10 +99,7 @@ Path.prototype.clone = function clone() {
Path.prototype.fromRaw = function fromRaw(data) { Path.prototype.fromRaw = function fromRaw(data) {
var p = new BufferReader(data); var p = new BufferReader(data);
this.wid = p.readU32();
this.account = p.readU32(); this.account = p.readU32();
this.change = -1;
this.index = -1;
this.keyType = p.readU8(); this.keyType = p.readU8();
switch (this.keyType) { switch (this.keyType) {
@ -144,9 +143,7 @@ Path.fromRaw = function fromRaw(data) {
Path.prototype.toRaw = function toRaw(writer) { Path.prototype.toRaw = function toRaw(writer) {
var p = new BufferWriter(writer); var p = new BufferWriter(writer);
p.writeU32(this.wid);
p.writeU32(this.account); p.writeU32(this.account);
p.writeU8(this.keyType); p.writeU8(this.keyType);
switch (this.keyType) { switch (this.keyType) {
@ -199,7 +196,7 @@ Path.prototype.fromHD = function fromHD(account, ring, change, index) {
this.keyType = Path.types.HD; this.keyType = Path.types.HD;
this.version = ring.witness ? 0 : -1; this.version = ring.witness ? 0 : -1;
this.type = ring.getType(); this.type = ring.getAddressType();
this.id = account.id; this.id = account.id;
this.hash = ring.getHash('hex'); this.hash = ring.getHash('hex');
@ -234,7 +231,7 @@ Path.prototype.fromKey = function fromKey(account, ring) {
this.keyType = Path.types.KEY; this.keyType = Path.types.KEY;
this.data = ring.toRaw(); this.data = ring.toRaw();
this.version = ring.witness ? 0 : -1; this.version = ring.witness ? 0 : -1;
this.type = ring.getType(); this.type = ring.getAddressType();
this.id = account.id; this.id = account.id;
this.hash = ring.getHash('hex'); this.hash = ring.getHash('hex');
return this; return this;
@ -312,8 +309,9 @@ Path.prototype.toAddress = function toAddress(network) {
Path.prototype.toJSON = function toJSON() { Path.prototype.toJSON = function toJSON() {
return { return {
name: this.name, name: this.name,
account: this.account,
change: this.change === 1, change: this.change === 1,
path: this.toPath() derivation: this.toPath()
}; };
}; };

View File

@ -341,12 +341,40 @@ TXDB.prototype.has = function has(key) {
* @returns {Promise} * @returns {Promise}
*/ */
TXDB.prototype.iterate = function iterate(options) { TXDB.prototype.range = function range(options) {
if (options.gte) if (options.gte)
options.gte = this.prefix(options.gte); options.gte = this.prefix(options.gte);
if (options.lte) if (options.lte)
options.lte = this.prefix(options.lte); options.lte = this.prefix(options.lte);
return this.db.iterate(options); return this.db.range(options);
};
/**
* Iterate.
* @param {Object} options
* @returns {Promise}
*/
TXDB.prototype.keys = function keys(options) {
if (options.gte)
options.gte = this.prefix(options.gte);
if (options.lte)
options.lte = this.prefix(options.lte);
return this.db.keys(options);
};
/**
* Iterate.
* @param {Object} options
* @returns {Promise}
*/
TXDB.prototype.values = function values(options) {
if (options.gte)
options.gte = this.prefix(options.gte);
if (options.lte)
options.lte = this.prefix(options.lte);
return this.db.values(options);
}; };
/** /**
@ -1307,7 +1335,7 @@ TXDB.prototype.getLocked = function getLocked() {
*/ */
TXDB.prototype.getHistoryHashes = function getHistoryHashes(account) { TXDB.prototype.getHistoryHashes = function getHistoryHashes(account) {
return this.iterate({ return this.keys({
gte: account != null gte: account != null
? layout.T(account, constants.NULL_HASH) ? layout.T(account, constants.NULL_HASH)
: layout.t(constants.NULL_HASH), : layout.t(constants.NULL_HASH),
@ -1332,7 +1360,7 @@ TXDB.prototype.getHistoryHashes = function getHistoryHashes(account) {
*/ */
TXDB.prototype.getUnconfirmedHashes = function getUnconfirmedHashes(account) { TXDB.prototype.getUnconfirmedHashes = function getUnconfirmedHashes(account) {
return this.iterate({ return this.keys({
gte: account != null gte: account != null
? layout.P(account, constants.NULL_HASH) ? layout.P(account, constants.NULL_HASH)
: layout.p(constants.NULL_HASH), : layout.p(constants.NULL_HASH),
@ -1357,7 +1385,7 @@ TXDB.prototype.getUnconfirmedHashes = function getUnconfirmedHashes(account) {
*/ */
TXDB.prototype.getOutpoints = function getOutpoints(account) { TXDB.prototype.getOutpoints = function getOutpoints(account) {
return this.iterate({ return this.keys({
gte: account != null gte: account != null
? layout.C(account, constants.NULL_HASH, 0) ? layout.C(account, constants.NULL_HASH, 0)
: layout.c(constants.NULL_HASH, 0), : layout.c(constants.NULL_HASH, 0),
@ -1397,7 +1425,7 @@ TXDB.prototype.getHeightRangeHashes = function getHeightRangeHashes(account, opt
start = options.start || 0; start = options.start || 0;
end = options.end || 0xffffffff; end = options.end || 0xffffffff;
return this.iterate({ return this.keys({
gte: account != null gte: account != null
? layout.H(account, start, constants.NULL_HASH) ? layout.H(account, start, constants.NULL_HASH)
: layout.h(start, constants.NULL_HASH), : layout.h(start, constants.NULL_HASH),
@ -1449,7 +1477,7 @@ TXDB.prototype.getRangeHashes = function getRangeHashes(account, options) {
start = options.start || 0; start = options.start || 0;
end = options.end || 0xffffffff; end = options.end || 0xffffffff;
return this.iterate({ return this.keys({
gte: account != null gte: account != null
? layout.M(account, start, constants.NULL_HASH) ? layout.M(account, start, constants.NULL_HASH)
: layout.m(start, constants.NULL_HASH), : layout.m(start, constants.NULL_HASH),
@ -1532,14 +1560,10 @@ TXDB.prototype.getHistory = function getHistory(account) {
return this.getAccountHistory(account); return this.getAccountHistory(account);
// Fast case // Fast case
return this.iterate({ return this.values({
gte: layout.t(constants.NULL_HASH), gte: layout.t(constants.NULL_HASH),
lte: layout.t(constants.HIGH_HASH), lte: layout.t(constants.HIGH_HASH),
keys: false, parse: bcoin.tx.fromExtended
values: true,
parse: function(key, value) {
return bcoin.tx.fromExtended(value);
}
}); });
}; };
@ -1607,10 +1631,9 @@ TXDB.prototype.getCoins = function getCoins(account) {
return this.getAccountCoins(account); return this.getAccountCoins(account);
// Fast case // Fast case
return this.iterate({ return this.range({
gte: layout.c(constants.NULL_HASH, 0), gte: layout.c(constants.NULL_HASH, 0x00000000),
lte: layout.c(constants.HIGH_HASH, 0xffffffff), lte: layout.c(constants.HIGH_HASH, 0xffffffff),
values: true,
parse: function(key, value) { parse: function(key, value) {
var parts = layout.cc(key); var parts = layout.cc(key);
var hash = parts[0]; var hash = parts[0];
@ -1663,10 +1686,9 @@ TXDB.prototype.fillHistory = function fillHistory(tx) {
hash = tx.hash('hex'); hash = tx.hash('hex');
return this.iterate({ return this.range({
gte: layout.d(hash, 0), gte: layout.d(hash, 0x00000000),
lte: layout.d(hash, 0xffffffff), lte: layout.d(hash, 0xffffffff),
values: true,
parse: function(key, value) { parse: function(key, value) {
var index = layout.dd(key)[1]; var index = layout.dd(key)[1];
var coin = bcoin.coin.fromRaw(value); var coin = bcoin.coin.fromRaw(value);
@ -1897,10 +1919,9 @@ TXDB.prototype.getBalance = co(function* getBalance(account) {
// Fast case // Fast case
balance = new Balance(this.wallet); balance = new Balance(this.wallet);
yield this.iterate({ yield this.range({
gte: layout.c(constants.NULL_HASH, 0), gte: layout.c(constants.NULL_HASH, 0x00000000),
lte: layout.c(constants.HIGH_HASH, 0xffffffff), lte: layout.c(constants.HIGH_HASH, 0xffffffff),
values: true,
parse: function(key, data) { parse: function(key, data) {
var parts = layout.cc(key); var parts = layout.cc(key);
var hash = parts[0]; var hash = parts[0];

View File

@ -403,6 +403,56 @@ Wallet.prototype._retoken = co(function* retoken(passphrase) {
return this.token; return this.token;
}); });
/**
* Rename the wallet.
* @param {String} id
* @returns {Promise}
*/
Wallet.prototype.rename = co(function* rename(id) {
var unlock = yield this.writeLock.lock();
try {
return yield this.db.rename(this, id);
} finally {
unlock();
}
});
/**
* Rename account.
* @param {(String|Number)?} account
* @param {String} name
* @returns {Promise}
*/
Wallet.prototype.renameAccount = co(function* renameAccount(account, name) {
var unlock = yield this.writeLock.lock();
try {
return yield this._renameAccount(account, name);
} finally {
unlock();
}
});
/**
* Rename account without a lock.
* @private
* @param {(String|Number)?} account
* @param {String} name
* @returns {Promise}
*/
Wallet.prototype._renameAccount = co(function* _renameAccount(account, name) {
assert(utils.isName(name), 'Bad account name.');
account = yield this.getAccount(account);
if (!account)
throw new Error('Account not found.');
yield this.db.renameAccount(account, name);
});
/** /**
* Lock the wallet, destroy decrypted key. * Lock the wallet, destroy decrypted key.
*/ */
@ -587,7 +637,7 @@ Wallet.prototype.getAccounts = function getAccounts() {
*/ */
Wallet.prototype.getAddressHashes = function getAddressHashes() { Wallet.prototype.getAddressHashes = function getAddressHashes() {
return this.db.getAddressHashes(this.wid); return this.db.getWalletHashes(this.wid);
}; };
/** /**
@ -759,7 +809,7 @@ Wallet.prototype.getPath = co(function* getPath(address) {
if (!hash) if (!hash)
return; return;
path = yield this.db.getAddressPath(this.wid, hash); path = yield this.db.getPath(this.wid, hash);
if (!path) if (!path)
return; return;

View File

@ -23,7 +23,8 @@ var MAX_POINT = String.fromCharCode(0xdbff, 0xdfff); // U+10FFFF
/* /*
* Database Layout: * Database Layout:
* p[addr-hash] -> path data * p[addr-hash] -> wallet ids
* P[wid][addr-hash] -> path data
* w[wid] -> wallet * w[wid] -> wallet
* l[id] -> wid * l[id] -> wid
* a[wid][index] -> account * a[wid][index] -> account
@ -44,6 +45,16 @@ var layout = {
pp: function(key) { pp: function(key) {
return key.toString('hex', 1); return key.toString('hex', 1);
}, },
P: function(wid, hash) {
var key = new Buffer(1 + 4 + (hash.length / 2));
key[0] = 0x50;
key.writeUInt32BE(wid, 1, true);
key.write(hash, 5, 'hex');
return key;
},
Pp: function(key) {
return key.toString('hex', 5);
},
w: function(wid) { w: function(wid) {
var key = new Buffer(5); var key = new Buffer(5);
key[0] = 0x77; key[0] = 0x77;
@ -140,9 +151,11 @@ function WalletDB(options) {
this.writeLock = new bcoin.locker(); this.writeLock = new bcoin.locker();
this.txLock = new bcoin.locker(); this.txLock = new bcoin.locker();
this.walletCache = new bcoin.lru(10000); this.widCache = new bcoin.lru(10000);
this.indexCache = new bcoin.lru(10000);
this.accountCache = new bcoin.lru(10000); this.accountCache = new bcoin.lru(10000);
this.pathCache = new bcoin.lru(100000); this.pathCache = new bcoin.lru(100000);
this.pathMapCache = new bcoin.lru(100000);
// Try to optimize for up to 1m addresses. // Try to optimize for up to 1m addresses.
// We use a regular bloom filter here // We use a regular bloom filter here
@ -276,9 +289,12 @@ WalletDB.prototype.getDepth = co(function* getDepth() {
*/ */
WalletDB.prototype.start = function start(wid) { WalletDB.prototype.start = function start(wid) {
var batch;
assert(utils.isNumber(wid), 'Bad ID for batch.'); assert(utils.isNumber(wid), 'Bad ID for batch.');
assert(!this.batches[wid], 'Batch already started.'); assert(!this.batches[wid], 'Batch already started.');
this.batches[wid] = this.db.batch(); batch = this.db.batch();
this.batches[wid] = batch;
return batch;
}; };
/** /**
@ -327,21 +343,27 @@ WalletDB.prototype.commit = function commit(wid) {
* @returns {Promise} * @returns {Promise}
*/ */
WalletDB.prototype.loadFilter = function loadFilter() { WalletDB.prototype.loadFilter = co(function* loadFilter() {
var self = this; var iter, item, hash;
if (!this.filter) if (!this.filter)
return Promise.resolve(null); return;
return this.db.iterate({ iter = this.db.iterator({
gte: layout.p(constants.NULL_HASH), gte: layout.p(constants.NULL_HASH),
lte: layout.p(constants.HIGH_HASH), lte: layout.p(constants.HIGH_HASH)
parse: function(key) {
var hash = layout.pp(key);
self.filter.add(hash, 'hex');
}
}); });
};
for (;;) {
item = yield iter.next();
if (!item)
break;
hash = layout.pp(item.key);
this.filter.add(hash, 'hex');
}
});
/** /**
* Test the bloom filter against an array of address hashes. * Test the bloom filter against an array of address hashes.
@ -369,18 +391,9 @@ WalletDB.prototype.testFilter = function testFilter(hashes) {
* @returns {Promise} - Returns Object. * @returns {Promise} - Returns Object.
*/ */
WalletDB.prototype.dump = co(function* dump() { WalletDB.prototype.dump = function dump() {
var records = {}; return this.db.dump();
yield this.db.iterate({ };
gte: new Buffer([0x00]),
lte: new Buffer([0xff]),
values: true,
parse: function(key, value) {
records[key.toString('hex')] = value.toString('hex');
}
});
return records;
});
/** /**
* Register an object with the walletdb. * Register an object with the walletdb.
@ -418,7 +431,7 @@ WalletDB.prototype.getWalletID = co(function* getWalletID(id) {
if (typeof id === 'number') if (typeof id === 'number')
return id; return id;
wid = this.walletCache.get(id); wid = this.widCache.get(id);
if (wid) if (wid)
return wid; return wid;
@ -430,7 +443,7 @@ WalletDB.prototype.getWalletID = co(function* getWalletID(id) {
wid = data.readUInt32LE(0, true); wid = data.readUInt32LE(0, true);
this.walletCache.set(id, wid); this.widCache.set(id, wid);
return wid; return wid;
}); });
@ -441,11 +454,10 @@ WalletDB.prototype.getWalletID = co(function* getWalletID(id) {
* @returns {Promise} - Returns {@link Wallet}. * @returns {Promise} - Returns {@link Wallet}.
*/ */
WalletDB.prototype.get = co(function* get(wid) { WalletDB.prototype.get = co(function* get(id) {
var wid = yield this.getWalletID(id);
var unlock; var unlock;
wid = yield this.getWalletID(wid);
if (!wid) if (!wid)
return; return;
@ -466,11 +478,8 @@ WalletDB.prototype.get = co(function* get(wid) {
*/ */
WalletDB.prototype._get = co(function* get(wid) { WalletDB.prototype._get = co(function* get(wid) {
var data, wallet; var wallet = this.wallets[wid];
var data;
// By the time the lock is released,
// the wallet may be watched.
wallet = this.wallets[wid];
if (wallet) if (wallet)
return wallet; return wallet;
@ -495,14 +504,123 @@ WalletDB.prototype._get = co(function* get(wid) {
*/ */
WalletDB.prototype.save = function save(wallet) { WalletDB.prototype.save = function save(wallet) {
var batch = this.batch(wallet.wid); var wid = wallet.wid;
var wid = new Buffer(4); var id = wallet.id;
this.walletCache.set(wallet.id, wallet.wid); var batch = this.batch(wid);
batch.put(layout.w(wallet.wid), wallet.toRaw()); var buf = new Buffer(4);
wid.writeUInt32LE(wallet.wid, 0, true);
batch.put(layout.l(wallet.id), wid); this.widCache.set(id, wid);
batch.put(layout.w(wid), wallet.toRaw());
buf.writeUInt32LE(wid, 0, true);
batch.put(layout.l(id), buf);
}; };
/**
* Rename a wallet.
* @param {Wallet} wallet
* @param {String} id
* @returns {Promise}
*/
WalletDB.prototype.rename = co(function* rename(wallet, id) {
var unlock = yield this.writeLock.lock();
try {
return yield this._rename(wallet, id);
} finally {
unlock();
}
});
/**
* Rename a wallet without a lock.
* @private
* @param {Wallet} wallet
* @param {String} id
* @returns {Promise}
*/
WalletDB.prototype._rename = co(function* _rename(wallet, id) {
var old = wallet.id;
var i, paths, path, batch;
assert(utils.isName(id), 'Bad wallet ID.');
if (yield this.has(id))
throw new Error('ID not available.');
this.widCache.remove(old);
paths = this.pathCache.values();
// TODO: Optimize this bullshit.
for (i = 0; i < paths.length; i++) {
path = paths[i];
if (path.wid !== wallet.wid)
continue;
path.id = id;
}
wallet.id = id;
batch = this.start(wallet.wid);
batch.del(layout.l(old));
this.save(wallet);
yield this.commit(wallet.wid);
});
/**
* Rename an account.
* @param {Account} account
* @param {String} name
* @returns {Promise}
*/
WalletDB.prototype.renameAccount = co(function* renameAccount(account, name) {
var old = account.name;
var key = account.wid + '/' + old;
var i, paths, path, batch;
assert(utils.isName(name), 'Bad account name.');
if (account.accountIndex === 0)
throw new Error('Cannot rename primary account.');
if (yield this.hasAccount(account.wid, name))
throw new Error('Account name not available.');
this.indexCache.remove(key);
paths = this.pathCache.values();
// TODO: Optimize this bullshit.
for (i = 0; i < paths.length; i++) {
path = paths[i];
if (path.wid !== account.wid)
continue;
if (path.account !== account.accountIndex)
continue;
path.name = name;
}
account.name = name;
batch = this.start(account.wid);
batch.del(layout.i(account.wid, old));
this.saveAccount(account);
yield this.commit(account.wid);
});
/** /**
* Test an api key against a wallet's api key. * Test an api key against a wallet's api key.
* @param {WalletID} wid * @param {WalletID} wid
@ -657,19 +775,20 @@ WalletDB.prototype._getAccount = co(function* getAccount(wid, index) {
WalletDB.prototype.getAccounts = co(function* getAccounts(wid) { WalletDB.prototype.getAccounts = co(function* getAccounts(wid) {
var map = []; var map = [];
var i, accounts; var i, items, item, name, index, accounts;
yield this.db.iterate({ items = yield this.db.range({
gte: layout.i(wid, ''), gte: layout.i(wid, ''),
lte: layout.i(wid, MAX_POINT), lte: layout.i(wid, MAX_POINT)
values: true,
parse: function(key, value) {
var name = layout.ii(key)[1];
var index = value.readUInt32LE(0, true);
map[index] = name;
}
}); });
for (i = 0; i < items.length; i++) {
item = items[i];
name = layout.ii(item.key)[1];
index = item.value.readUInt32LE(0, true);
map[index] = name;
}
// Get it out of hash table mode. // Get it out of hash table mode.
accounts = []; accounts = [];
@ -689,7 +808,7 @@ WalletDB.prototype.getAccounts = co(function* getAccounts(wid) {
*/ */
WalletDB.prototype.getAccountIndex = co(function* getAccountIndex(wid, name) { WalletDB.prototype.getAccountIndex = co(function* getAccountIndex(wid, name) {
var index; var key, index;
if (!wid) if (!wid)
return -1; return -1;
@ -700,12 +819,22 @@ WalletDB.prototype.getAccountIndex = co(function* getAccountIndex(wid, name) {
if (typeof name === 'number') if (typeof name === 'number')
return name; return name;
key = wid + '/' + name;
index = this.indexCache.get(key);
if (index != null)
return index;
index = yield this.db.get(layout.i(wid, name)); index = yield this.db.get(layout.i(wid, name));
if (!index) if (!index)
return -1; return -1;
return index.readUInt32LE(0, true); index = index.readUInt32LE(0, true);
this.indexCache.set(key, index);
return index;
}); });
/** /**
@ -731,14 +860,17 @@ WalletDB.prototype.getAccountName = co(function* getAccountName(wid, index) {
*/ */
WalletDB.prototype.saveAccount = function saveAccount(account) { WalletDB.prototype.saveAccount = function saveAccount(account) {
var batch = this.batch(account.wid); var wid = account.wid;
var index = new Buffer(4); var index = account.accountIndex;
var key = account.wid + '/' + account.accountIndex; var name = account.name;
var batch = this.batch(wid);
var key = wid + '/' + index;
var buf = new Buffer(4);
index.writeUInt32LE(account.accountIndex, 0, true); buf.writeUInt32LE(index, 0, true);
batch.put(layout.a(account.wid, account.accountIndex), account.toRaw()); batch.put(layout.a(wid, index), account.toRaw());
batch.put(layout.i(account.wid, account.name), index); batch.put(layout.i(wid, name), buf);
this.accountCache.set(key, account); this.accountCache.set(key, account);
}; };
@ -794,10 +926,34 @@ WalletDB.prototype.hasAccount = co(function* hasAccount(wid, account) {
return yield this.db.has(layout.a(wid, index)); return yield this.db.has(layout.a(wid, index));
}); });
/**
* Lookup the corresponding account name's index.
* @param {WalletID} wid
* @param {String|Number} name - Account name/index.
* @returns {Promise} - Returns Number.
*/
WalletDB.prototype.getWalletsByHash = co(function* getWalletsByHash(hash) {
var wallets = this.pathMapCache.get(hash);
var data;
if (wallets)
return wallets;
data = yield this.db.get(layout.p(hash));
if (!data)
return;
wallets = parseWallets(data);
this.pathMapCache.get(hash, wallets);
return wallets;
});
/** /**
* Save addresses to the path map. * Save addresses to the path map.
* The path map exists in the form of:
* `p/[address-hash] -> {walletid1=path1, walletid2=path2, ...}`
* @param {WalletID} wid * @param {WalletID} wid
* @param {KeyRing[]} rings * @param {KeyRing[]} rings
* @returns {Promise} * @returns {Promise}
@ -810,7 +966,7 @@ WalletDB.prototype.saveAddress = co(function* saveAddress(wid, rings) {
ring = rings[i]; ring = rings[i];
path = ring.path; path = ring.path;
yield this.writeAddress(wid, ring.getAddress(), path); yield this.writePath(wid, path);
if (!ring.witness) if (!ring.witness)
continue; continue;
@ -820,45 +976,17 @@ WalletDB.prototype.saveAddress = co(function* saveAddress(wid, rings) {
path.version = -1; path.version = -1;
path.type = Script.types.SCRIPTHASH; path.type = Script.types.SCRIPTHASH;
yield this.writeAddress(wid, ring.getProgramAddress(), path); yield this.writePath(wid, path);
} }
}); });
/**
* Save a single address to the path map.
* @param {WalletID} wid
* @param {KeyRing} rings
* @param {Path} path
* @returns {Promise}
*/
WalletDB.prototype.writeAddress = co(function* writeAddress(wid, address, path) {
var hash = address.getHash('hex');
var batch = this.batch(wid);
var paths;
if (this.filter)
this.filter.add(hash, 'hex');
this.emit('save address', address, path);
paths = yield this.getAddressPaths(hash);
if (!paths)
paths = {};
if (paths[wid])
return;
paths[wid] = path;
this.pathCache.set(hash, paths);
batch.put(layout.p(hash), serializePaths(paths));
});
/** /**
* Save paths to the path map. * Save paths to the path map.
*
* The path map exists in the form of:
* - `p[address-hash] -> wids`
* - `P[wid][address-hash] -> path`
*
* @param {WalletID} wid * @param {WalletID} wid
* @param {Path[]} paths * @param {Path[]} paths
* @returns {Promise} * @returns {Promise}
@ -883,26 +1011,29 @@ WalletDB.prototype.savePath = co(function* savePath(wid, paths) {
WalletDB.prototype.writePath = co(function* writePath(wid, path) { WalletDB.prototype.writePath = co(function* writePath(wid, path) {
var hash = path.hash; var hash = path.hash;
var batch = this.batch(wid); var batch = this.batch(wid);
var paths; var key = wid + hash;
var wallets;
if (this.filter) if (this.filter)
this.filter.add(hash, 'hex'); this.filter.add(hash, 'hex');
this.emit('save address', path.toAddress(), path); this.emit('save address', path.toAddress(), path);
paths = yield this.getAddressPaths(hash); wallets = yield this.getWalletsByHash(hash);
if (!paths) if (!wallets)
paths = {}; wallets = [];
if (paths[wid]) if (wallets.indexOf(wid) !== -1)
return; return;
paths[wid] = path; wallets.push(wid);
this.pathCache.set(hash, paths); this.pathMapCache.set(hash, wallets);
this.pathCache.set(key, path);
batch.put(layout.p(hash), serializePaths(paths)); batch.put(layout.p(hash), serializeWallets(wallets));
batch.put(layout.P(wid, hash), path.toRaw());
}); });
/** /**
@ -911,47 +1042,57 @@ WalletDB.prototype.writePath = co(function* writePath(wid, path) {
* @returns {Promise} * @returns {Promise}
*/ */
WalletDB.prototype.getAddressPaths = co(function* getAddressPaths(hash) { WalletDB.prototype.getPaths = co(function* getPaths(hash) {
var paths, data; var wallets = yield this.getWalletsByHash(hash);
var i, wid, path, paths;
if (!hash) if (!wallets)
return; return;
paths = this.pathCache.get(hash); paths = [];
if (paths) for (i = 0; i < wallets.length; i++) {
return paths; wid = wallets[i];
path = yield this.getPath(wid, hash);
data = yield this.db.get(layout.p(hash)); if (path)
paths.push(path);
if (!data) }
return;
paths = parsePaths(data, hash);
yield this.fillPathNames(paths);
this.pathCache.set(hash, paths);
return paths; return paths;
}); });
/** /**
* Assign account names to an array of paths. * Retrieve path by hash.
* @param {Path[]} paths * @param {WalletID} wid
* @param {Hash} hash
* @returns {Promise} * @returns {Promise}
*/ */
WalletDB.prototype.fillPathNames = co(function* fillPathNames(paths) { WalletDB.prototype.getPath = co(function* getPath(wid, hash) {
var i, path; var key, path, data;
for (i = 0; i < paths.length; i++) { if (!hash)
path = paths[i]; return;
if (path.name)
continue; key = wid + hash;
// These should be mostly cached. path = this.pathCache.get(key);
path.name = yield this.db.getAccountName(path.wid, path.account);
} if (path)
return path;
data = yield this.db.get(layout.P(wid, hash));
if (!data)
return;
path = Path.fromRaw(data);
path.wid = wid;
path.hash = hash;
path.name = yield this.getAccountName(wid, path.account);
this.pathCache.set(key, path);
return path;
}); });
/** /**
@ -963,33 +1104,34 @@ WalletDB.prototype.fillPathNames = co(function* fillPathNames(paths) {
*/ */
WalletDB.prototype.hasAddress = co(function* hasAddress(wid, hash) { WalletDB.prototype.hasAddress = co(function* hasAddress(wid, hash) {
var paths = yield this.getAddressPaths(hash); var path = yield this.getPath(wid, hash);
return path != null;
if (!paths || !paths[wid])
return false;
return true;
}); });
/**
* Get all address hashes.
* @returns {Promise}
*/
WalletDB.prototype.getHashes = function getHashes() {
return this.db.keys({
gte: layout.p(constants.NULL_HASH),
lte: layout.p(constants.HIGH_HASH),
parse: layout.pp
});
};
/** /**
* Get all address hashes. * Get all address hashes.
* @param {WalletID} wid * @param {WalletID} wid
* @returns {Promise} * @returns {Promise}
*/ */
WalletDB.prototype.getAddressHashes = function getAddressHashes(wid) { WalletDB.prototype.getWalletHashes = function getWalletHashes(wid) {
return this.db.iterate({ return this.db.keys({
gte: layout.p(constants.NULL_HASH), gte: layout.P(wid, constants.NULL_HASH),
lte: layout.p(constants.HIGH_HASH), lte: layout.P(wid, constants.HIGH_HASH),
values: true, parse: layout.Pp
parse: function(key, value) {
var paths = parsePaths(value);
if (wid && !paths[wid])
return;
return layout.pp(key);
}
}); });
}; };
@ -1000,25 +1142,26 @@ WalletDB.prototype.getAddressHashes = function getAddressHashes(wid) {
*/ */
WalletDB.prototype.getWalletPaths = co(function* getWalletPaths(wid) { WalletDB.prototype.getWalletPaths = co(function* getWalletPaths(wid) {
var paths = yield this.db.iterate({ var i, item, items, hash, path;
gte: layout.p(constants.NULL_HASH),
lte: layout.p(constants.HIGH_HASH),
values: true,
parse: function(key, value) {
var hash = layout.pp(key);
var paths = parsePaths(value, hash);
var path = paths[wid];
if (!path) items = yield this.db.range({
return; gte: layout.P(wid, constants.NULL_HASH),
lte: layout.P(wid, constants.HIGH_HASH)
return path;
}
}); });
yield this.fillPathNames(paths); for (i = 0; i < items.length; i++) {
item = items[i];
hash = layout.Pp(item.key);
path = Path.fromRaw(item.value);
return paths; path.hash = hash;
path.wid = wid;
path.name = yield this.getAccountName(wid, path.account);
items[i] = path;
}
return items;
}); });
/** /**
@ -1027,12 +1170,10 @@ WalletDB.prototype.getWalletPaths = co(function* getWalletPaths(wid) {
*/ */
WalletDB.prototype.getWallets = function getWallets() { WalletDB.prototype.getWallets = function getWallets() {
return this.db.iterate({ return this.db.keys({
gte: layout.l(''), gte: layout.l(''),
lte: layout.l(MAX_POINT), lte: layout.l(MAX_POINT),
parse: function(key) { parse: layout.ll
return layout.ll(key);
}
}); });
}; };
@ -1067,7 +1208,7 @@ WalletDB.prototype._rescan = co(function* rescan(chaindb, height) {
if (height == null) if (height == null)
height = this.height; height = this.height;
hashes = yield this.getAddressHashes(); hashes = yield this.getHashes();
this.logger.info('Scanning for %d addresses.', hashes.length); this.logger.info('Scanning for %d addresses.', hashes.length);
@ -1082,33 +1223,46 @@ WalletDB.prototype._rescan = co(function* rescan(chaindb, height) {
* @returns {Promise} * @returns {Promise}
*/ */
WalletDB.prototype.getPendingKeys = function getPendingKeys() { WalletDB.prototype.getPendingKeys = co(function* getPendingKeys() {
var layout = require('./txdb').layout; var layout = require('./txdb').layout;
var dummy = new Buffer(0); var dummy = new Buffer(0);
var uniq = {}; var uniq = {};
var keys = [];
var result = [];
var i, iter, item, key, wid, hash;
return this.db.iterate({ iter = yield this.db.iterator({
gte: layout.prefix(0x00000000, dummy), gte: layout.prefix(0x00000000, dummy),
lte: layout.prefix(0xffffffff, dummy), lte: layout.prefix(0xffffffff, dummy)
keys: true,
parse: function(key) {
var wid, hash;
if (key[5] !== 0x70)
return;
wid = layout.pre(key);
hash = layout.pp(key);
if (uniq[hash])
return;
uniq[hash] = true;
return layout.prefix(wid, layout.t(hash));
}
}); });
};
for (;;) {
item = yield iter.next();
if (!item)
break;
if (item.key[5] === 0x70)
keys.push(item.key);
}
for (i = 0; i < keys.length; i++) {
key = keys[i];
wid = layout.pre(key);
hash = layout.pp(key);
if (uniq[hash])
continue;
uniq[hash] = true;
key = layout.prefix(wid, layout.t(hash));
result.push(key);
}
return result;
});
/** /**
* Resend all pending transactions. * Resend all pending transactions.
@ -1189,22 +1343,14 @@ WalletDB.prototype.getTable = co(function* getTable(hashes) {
for (i = 0; i < hashes.length; i++) { for (i = 0; i < hashes.length; i++) {
hash = hashes[i]; hash = hashes[i];
paths = yield this.getAddressPaths(hash); paths = yield this.getPaths(hash);
if (!paths) { if (!paths) {
assert(!table[hash]);
table[hash] = []; table[hash] = [];
continue; continue;
} }
keys = Object.keys(paths); table[hash] = paths;
values = [];
for (j = 0; j < keys.length; j++)
values.push(paths[keys[j]]);
assert(!table[hash]);
table[hash] = values;
match = true; match = true;
} }
@ -1522,28 +1668,6 @@ WalletDB.prototype._addTX = co(function* addTX(tx, force) {
return wallets; return wallets;
}); });
/**
* Get the corresponding path for an address hash.
* @param {WalletID} wid
* @param {Hash} hash
* @returns {Promise}
*/
WalletDB.prototype.getAddressPath = co(function* getAddressPath(wid, hash) {
var paths = yield this.getAddressPaths(hash);
var path;
if (!paths)
return;
path = paths[wid];
if (!path)
return;
return path;
});
/** /**
* Path Info * Path Info
* @constructor * @constructor
@ -1924,35 +2048,6 @@ WalletBlock.prototype.toJSON = function toJSON() {
* Helpers * Helpers
*/ */
function parsePaths(data, hash) {
var p = new BufferReader(data);
var out = {};
var path;
while (p.left()) {
path = Path.fromRaw(p);
out[path.wid] = path;
if (hash)
path.hash = hash;
}
return out;
}
function serializePaths(out) {
var p = new BufferWriter();
var keys = Object.keys(out);
var i, wid, path;
for (i = 0; i < keys.length; i++) {
wid = keys[i];
path = out[wid];
path.toRaw(p);
}
return p.render();
}
function parseWallets(data) { function parseWallets(data) {
var p = new BufferReader(data); var p = new BufferReader(data);
var wallets = []; var wallets = [];

View File

@ -953,6 +953,14 @@ describe('Wallet', function() {
assert.equal(details[0].toJSON().outputs[0].path.name, 'foo'); assert.equal(details[0].toJSON().outputs[0].path.name, 'foo');
})); }));
it('should rename wallet', cob(function *() {
var w = wallet;
yield wallet.rename('test');
var txs = yield w.getRange('foo', { start: 0xdeadbeef - 1000 });
var details = yield w.toDetails(txs);
assert.equal(details[0].toJSON().id, 'test');
}));
it('should cleanup', cob(function *() { it('should cleanup', cob(function *() {
var records = yield walletdb.dump(); var records = yield walletdb.dump();
constants.tx.COINBASE_MATURITY = 100; constants.tx.COINBASE_MATURITY = 100;