wallet: use batches.

This commit is contained in:
Christopher Jeffrey 2017-10-16 18:25:07 -07:00
parent 969fd8f704
commit 55f5ff9493
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
12 changed files with 963 additions and 1261 deletions

View File

@ -11,6 +11,7 @@ const assert = require('assert');
const LOW = Buffer.from([0x00]);
const HIGH = Buffer.from([0xff]);
const DUMMY = Buffer.alloc(0);
let VERSION_ERROR;
@ -50,7 +51,6 @@ function LowlevelUp(backend, location, options) {
/**
* Initialize the database.
* @method
* @private
*/
@ -293,6 +293,19 @@ LowlevelUp.prototype.batch = function batch() {
return new Batch(this);
};
/**
* Create a bucket.
* @param {Buffer} [prefix=DUMMY]
* @returns {Bucket}
*/
LowlevelUp.prototype.bucket = function bucket(prefix) {
if (!this.loaded)
throw new Error('Database is closed.');
return new Bucket(this, this.batch(), prefix);
};
/**
* Create an iterator.
* @param {Object} options
@ -376,7 +389,6 @@ LowlevelUp.prototype.compactRange = function compactRange(start, end) {
/**
* Test whether a key exists.
* @method
* @param {String} key
* @returns {Promise} - Returns Boolean.
*/
@ -388,89 +400,54 @@ LowlevelUp.prototype.has = async function has(key) {
/**
* Collect all keys from iterator options.
* @method
* @param {Object} options - Iterator options.
* @returns {Promise} - Returns Array.
*/
LowlevelUp.prototype.range = async function range(options) {
LowlevelUp.prototype.range = function range(options) {
const iter = this.iterator({
gte: options.gte,
lte: options.lte,
keys: true,
values: true
});
const items = [];
await iter.each((key, value) => {
if (options.parse) {
const item = options.parse(key, value);
if (item)
items.push(item);
} else {
items.push(new IteratorItem(key, value));
}
});
return items;
return iter.range(options.parse);
};
/**
* Collect all keys from iterator options.
* @method
* @param {Object} options - Iterator options.
* @returns {Promise} - Returns Array.
*/
LowlevelUp.prototype.keys = async function keys(options) {
LowlevelUp.prototype.keys = function keys(options) {
const iter = this.iterator({
gte: options.gte,
lte: options.lte,
keys: true,
values: false
});
const items = [];
await iter.each((key) => {
if (options.parse)
key = options.parse(key);
items.push(key);
});
return items;
return iter.keys(options.parse);
};
/**
* Collect all keys from iterator options.
* @method
* @param {Object} options - Iterator options.
* @returns {Promise} - Returns Array.
*/
LowlevelUp.prototype.values = async function values(options) {
LowlevelUp.prototype.values = function values(options) {
const iter = this.iterator({
gte: options.gte,
lte: options.lte,
keys: false,
values: true
});
const items = [];
await iter.each((value) => {
if (options.parse)
value = options.parse(value);
items.push(value);
});
return items;
return iter.values(options.parse);
};
/**
* Dump database (for debugging).
* @method
* @returns {Promise} - Returns Object.
*/
@ -493,7 +470,6 @@ LowlevelUp.prototype.dump = async function dump() {
/**
* Write and assert a version number for the database.
* @method
* @param {Number} version
* @returns {Promise}
*/
@ -518,7 +494,6 @@ LowlevelUp.prototype.checkVersion = async function checkVersion(key, version) {
/**
* Clone the database.
* @method
* @param {String} path
* @returns {Promise}
*/
@ -631,6 +606,168 @@ Batch.prototype.clear = function clear() {
return this;
};
/**
* Bucket
* @constructor
* @ignore
* @param {LowlevelUp} db
* @param {Batch} batch
* @param {Buffer} [prefix=DUMMY]
*/
function Bucket(db, batch, prefix, parent) {
this.db = db;
this.batch = batch;
this.prefix = prefix || DUMMY;
this.parent = parent || DUMMY;
}
/**
* Get child bucket.
* @param {Buffer} [prefix=DUMMY]
*/
Bucket.prototype.batch = function batch() {
return new Bucket(this.db, this.batch, this.prefix, this.parent);
};
/**
* Get child bucket.
* @param {Buffer} [prefix=DUMMY]
*/
Bucket.prototype.bucket = function bucket(prefix = DUMMY) {
const parent = this.prefix;
const child = concat(parent, prefix);
return new Bucket(this.db, this.batch, child, parent);
};
/**
* Get child bucket.
* @param {Buffer} [prefix=DUMMY]
*/
Bucket.prototype.up = function up() {
return new Bucket(this.db, this.batch, this.parent);
};
/**
* Get a value from the bucket.
* @param {String|Buffer} key
* @returns {Promise}
*/
Bucket.prototype.has = function has(key) {
return this.db.has(concat(this.prefix, key));
};
/**
* Get a value from the bucket.
* @param {String|Buffer} key
* @returns {Promise}
*/
Bucket.prototype.get = function get(key) {
return this.db.get(concat(this.prefix, key));
};
/**
* Create an iterator.
* @param {Object} options
* @returns {Iterator}
*/
Bucket.prototype.iterator = function iterator(options) {
return new Iterator(this.db, options, this.prefix);
};
/**
* Collect all keys from iterator options.
* @param {Object} options - Iterator options.
* @returns {Promise} - Returns Array.
*/
Bucket.prototype.range = function range(options = {}) {
const iter = this.iterator({
gte: options.gte,
lte: options.lte,
keys: true,
values: true
});
return iter.range(options.parse);
};
/**
* Collect all keys from iterator options.
* @param {Object} options - Iterator options.
* @returns {Promise} - Returns Array.
*/
Bucket.prototype.keys = function keys(options = {}) {
const iter = this.iterator({
gte: options.gte,
lte: options.lte,
keys: true,
values: false
});
return iter.keys(options.parse);
};
/**
* Collect all keys from iterator options.
* @param {Object} options - Iterator options.
* @returns {Promise} - Returns Array.
*/
Bucket.prototype.values = function values(options = {}) {
const iter = this.iterator({
gte: options.gte,
lte: options.lte,
keys: false,
values: true
});
return iter.values(options.parse);
};
/**
* Write a value to the bucket.
* @param {String|Buffer} key
* @param {Buffer} value
*/
Bucket.prototype.put = function put(key, value) {
this.batch.put(concat(this.prefix, key), value);
return this;
};
/**
* Delete a value from the bucket.
* @param {String|Buffer} key
*/
Bucket.prototype.del = function del(key) {
this.batch.del(concat(this.prefix, key));
return this;
};
/**
* Write batch to database.
* @returns {Promise}
*/
Bucket.prototype.write = function write() {
return this.batch.write();
};
/**
* Clear the batch.
*/
Bucket.prototype.clear = function clear() {
this.batch.clear();
return this;
};
/**
* Iterator
* @constructor
@ -639,8 +776,9 @@ Batch.prototype.clear = function clear() {
* @param {Object} options
*/
function Iterator(db, options) {
this.options = new IteratorOptions(options);
function Iterator(db, options, prefix) {
this.prefix = prefix || DUMMY;
this.options = new IteratorOptions(options, prefix);
this.options.keyAsBuffer = db.options.bufferKeys;
this.iter = db.binding.iterator(this.options);
@ -681,7 +819,7 @@ Iterator.prototype.each = async function each(cb) {
await this.read();
while (this.cache.length > 0) {
const key = this.cache.pop();
const key = slice(this.prefix, this.cache.pop());
const value = this.cache.pop();
let result = null;
@ -725,7 +863,7 @@ Iterator.prototype.next = async function next() {
}
if (this.cache.length > 0) {
this.key = this.cache.pop();
this.key = slice(this.prefix, this.cache.pop());
this.value = this.cache.pop();
return true;
}
@ -803,6 +941,64 @@ Iterator.prototype.end = function end() {
});
};
/**
* Collect all keys from iterator options.
* @param {Object} options - Iterator options.
* @returns {Promise} - Returns Array.
*/
Iterator.prototype.range = async function range(parse) {
const items = [];
await this.each((key, value) => {
if (parse) {
const item = parse(key, value);
if (item)
items.push(item);
} else {
items.push(new IteratorItem(key, value));
}
});
return items;
};
/**
* Collect all keys from iterator options.
* @param {Object} options - Iterator options.
* @returns {Promise} - Returns Array.
*/
Iterator.prototype.keys = async function keys(parse) {
const items = [];
await this.each((key) => {
if (parse)
key = parse(key);
items.push(key);
});
return items;
};
/**
* Collect all keys from iterator options.
* @param {Object} options - Iterator options.
* @returns {Promise} - Returns Array.
*/
Iterator.prototype.values = async function values(parse) {
const items = [];
await this.each((value) => {
if (parse)
value = parse(value);
items.push(value);
});
return items;
};
/**
* Iterator Item
* @ignore
@ -948,7 +1144,7 @@ LLUOptions.prototype.fromOptions = function fromOptions(options) {
* @param {Object} options
*/
function IteratorOptions(options) {
function IteratorOptions(options, prefix) {
this.gte = null;
this.lte = null;
this.gt = null;
@ -964,8 +1160,7 @@ function IteratorOptions(options) {
// Note: do not add this property.
// this.limit = null;
if (options)
this.fromOptions(options);
this.fromOptions(options || {}, prefix || DUMMY);
}
/**
@ -975,27 +1170,44 @@ function IteratorOptions(options) {
* @returns {IteratorOptions}
*/
IteratorOptions.prototype.fromOptions = function fromOptions(options) {
IteratorOptions.prototype.fromOptions = function fromOptions(options, prefix) {
assert(options, 'Options are required.');
if (options.gte != null) {
assert(Buffer.isBuffer(options.gte) || typeof options.gte === 'string');
this.gte = options.gte;
this.gte = concat(prefix, options.gte);
}
if (options.lte != null) {
assert(Buffer.isBuffer(options.lte) || typeof options.lte === 'string');
this.lte = options.lte;
this.lte = concat(prefix, options.lte);
}
if (options.gt != null) {
assert(Buffer.isBuffer(options.gt) || typeof options.gt === 'string');
this.gt = options.gt;
this.gt = concat(prefix, options.gt);
}
if (options.lt != null) {
assert(Buffer.isBuffer(options.lt) || typeof options.lt === 'string');
this.lt = options.lt;
this.lt = concat(prefix, options.lt);
}
if (prefix.length > 0) {
if (!this.gt && !this.gte)
this.gt = prefix;
if (!this.lt && !this.lte) {
const pre = Buffer.from(prefix);
for (let i = pre.length - 1; i >= 0; i--) {
if (pre[i] !== 0xff) {
pre[i] += 1;
break;
}
pre[i] = 0;
}
this.lt = pre;
}
}
if (options.keys != null) {
@ -1058,6 +1270,54 @@ function wrap(resolve, reject) {
};
}
function slice(prefix, key) {
if (!key || key.length === 0)
return key;
if (prefix.length === 0)
return key;
if (typeof key === 'string') {
if (Buffer.isBuffer(prefix))
prefix = prefix.toString('ascii');
assert(typeof prefix === 'string');
assert(key.length > prefix.length);
return key.slice(prefix.length);
}
assert(Buffer.isBuffer(key));
assert(key.length > prefix.length);
return key.slice(prefix.length);
}
function concat(prefix, key) {
if (prefix.length === 0)
return key;
if (typeof key === 'string') {
if (Buffer.isBuffer(prefix))
prefix = prefix.toString('ascii');
assert(typeof prefix === 'string');
return prefix + key;
}
assert(Buffer.isBuffer(key));
const data = Buffer.allocUnsafe(prefix.length + key.length);
if (typeof prefix === 'string') {
data.write(prefix, 0, 'ascii');
} else {
assert(Buffer.isBuffer(prefix));
prefix.copy(data, 0);
}
key.copy(data, prefix.length);
return data;
}
VERSION_ERROR = 'Warning:'
+ ' Your database does not match the current database version.'
+ ' This is likely because the database layout or serialization'

View File

@ -667,13 +667,13 @@ HTTPClient.prototype.retoken = async function retoken(id, passphrase) {
/**
* Change or set master key's passphrase.
* @param {String|Buffer} passphrase
* @param {(String|Buffer)?} old
* @param {String|Buffer} new_
* @returns {Promise}
*/
HTTPClient.prototype.setPassphrase = function setPassphrase(id, old, new_) {
const body = { old: old, new: new_ };
HTTPClient.prototype.setPassphrase = function setPassphrase(id, passphrase, old) {
const body = { passphrase , old };
return this._post(`/wallet/${id}/passphrase`, body);
};

View File

@ -336,8 +336,8 @@ HTTPWallet.prototype.createNested = function createNested(account) {
* @see Wallet#setPassphrase
*/
HTTPWallet.prototype.setPassphrase = function setPassphrase(old, new_) {
return this.client.setPassphrase(this.id, old, new_);
HTTPWallet.prototype.setPassphrase = function setPassphrase(passphrase, old) {
return this.client.setPassphrase(this.id, passphrase, old);
};
/**

View File

@ -48,7 +48,6 @@ function Account(wdb, options) {
this.wdb = wdb;
this.network = wdb.network;
this.wallet = null;
this.receive = null;
this.change = null;
@ -226,11 +225,11 @@ Account.MAX_LOOKAHEAD = 40;
* @returns {Promise}
*/
Account.prototype.init = async function init() {
Account.prototype.init = async function init(b) {
// Waiting for more keys.
if (this.keys.length !== this.n - 1) {
assert(!this.initialized);
this.save();
this.save(b);
return;
}
@ -240,7 +239,7 @@ Account.prototype.init = async function init() {
this.initialized = true;
await this.initDepth();
await this.initDepth(b);
};
/**
@ -341,7 +340,7 @@ Account.prototype.spliceKey = function spliceKey(key) {
* @returns {Promise}
*/
Account.prototype.addSharedKey = async function addSharedKey(key) {
Account.prototype.addSharedKey = async function addSharedKey(b, key) {
const result = this.pushKey(key);
if (await this.hasDuplicate()) {
@ -350,7 +349,7 @@ Account.prototype.addSharedKey = async function addSharedKey(key) {
}
// Try to initialize again.
await this.init();
await this.init(b);
return result;
};
@ -361,14 +360,14 @@ Account.prototype.addSharedKey = async function addSharedKey(key) {
* @returns {Promise}
*/
Account.prototype.hasDuplicate = function hasDuplicate() {
Account.prototype.hasDuplicate = async function hasDuplicate() {
if (this.keys.length !== this.n - 1)
return false;
const ring = this.deriveReceive(0);
const hash = ring.getScriptHash('hex');
return this.wallet.hasAddress(hash);
return this.wdb.hasPath(this.wid, hash);
};
/**
@ -378,13 +377,13 @@ Account.prototype.hasDuplicate = function hasDuplicate() {
* @returns {Promise}
*/
Account.prototype.removeSharedKey = function removeSharedKey(key) {
Account.prototype.removeSharedKey = function removeSharedKey(b, key) {
const result = this.spliceKey(key);
if (!result)
return false;
this.save();
this.save(b);
return true;
};
@ -422,28 +421,28 @@ Account.prototype.createNested = function createNested() {
* @returns {Promise} - Returns {@link WalletKey}.
*/
Account.prototype.createKey = async function createKey(branch) {
Account.prototype.createKey = async function createKey(b, branch) {
let key, lookahead;
switch (branch) {
case 0:
key = this.deriveReceive(this.receiveDepth);
lookahead = this.deriveReceive(this.receiveDepth + this.lookahead);
await this.saveKey(lookahead);
await this.saveKey(b, lookahead);
this.receiveDepth++;
this.receive = key;
break;
case 1:
key = this.deriveChange(this.changeDepth);
lookahead = this.deriveReceive(this.changeDepth + this.lookahead);
await this.saveKey(lookahead);
await this.saveKey(b, lookahead);
this.changeDepth++;
this.change = key;
break;
case 2:
key = this.deriveNested(this.nestedDepth);
lookahead = this.deriveNested(this.nestedDepth + this.lookahead);
await this.saveKey(lookahead);
await this.saveKey(b, lookahead);
this.nestedDepth++;
this.nested = key;
break;
@ -570,8 +569,8 @@ Account.prototype.deriveKey = function deriveKey(branch, index, master) {
* @returns {Promise}
*/
Account.prototype.save = function save() {
return this.wdb.saveAccount(this);
Account.prototype.save = function save(b) {
return this.wdb.saveAccount(b, this);
};
/**
@ -580,8 +579,8 @@ Account.prototype.save = function save() {
* @returns {Promise}
*/
Account.prototype.saveKey = function saveKey(ring) {
return this.wdb.saveKey(this.wallet, ring);
Account.prototype.saveKey = function saveKey(b, ring) {
return this.wdb.saveKey(b, this.wid, ring);
};
/**
@ -590,8 +589,8 @@ Account.prototype.saveKey = function saveKey(ring) {
* @returns {Promise}
*/
Account.prototype.savePath = function savePath(path) {
return this.wdb.savePath(this.wallet, path);
Account.prototype.savePath = function savePath(b, path) {
return this.wdb.savePath(b, this.wid, path);
};
/**
@ -599,29 +598,29 @@ Account.prototype.savePath = function savePath(path) {
* @returns {Promise}
*/
Account.prototype.initDepth = async function initDepth() {
Account.prototype.initDepth = async function initDepth(b) {
// Receive Address
this.receive = this.deriveReceive(0);
this.receiveDepth = 1;
await this.saveKey(this.receive);
await this.saveKey(b, this.receive);
// Lookahead
for (let i = 0; i < this.lookahead; i++) {
const key = this.deriveReceive(i + 1);
await this.saveKey(key);
await this.saveKey(b, key);
}
// Change Address
this.change = this.deriveChange(0);
this.changeDepth = 1;
await this.saveKey(this.change);
await this.saveKey(b, this.change);
// Lookahead
for (let i = 0; i < this.lookahead; i++) {
const key = this.deriveChange(i + 1);
await this.saveKey(key);
await this.saveKey(b, key);
}
// Nested Address
@ -629,16 +628,16 @@ Account.prototype.initDepth = async function initDepth() {
this.nested = this.deriveNested(0);
this.nestedDepth = 1;
await this.saveKey(this.nested);
await this.saveKey(b, this.nested);
// Lookahead
for (let i = 0; i < this.lookahead; i++) {
const key = this.deriveNested(i + 1);
await this.saveKey(key);
await this.saveKey(b, key);
}
}
this.save();
this.save(b);
};
/**
@ -649,7 +648,7 @@ Account.prototype.initDepth = async function initDepth() {
* @returns {Promise} - Returns {@link WalletKey}.
*/
Account.prototype.syncDepth = async function syncDepth(receive, change, nested) {
Account.prototype.syncDepth = async function syncDepth(b, receive, change, nested) {
let derived = false;
let result = null;
@ -660,7 +659,7 @@ Account.prototype.syncDepth = async function syncDepth(receive, change, nested)
for (let i = depth; i < receive + this.lookahead; i++) {
const key = this.deriveReceive(i);
await this.saveKey(key);
await this.saveKey(b, key);
}
this.receive = this.deriveReceive(receive - 1);
@ -677,7 +676,7 @@ Account.prototype.syncDepth = async function syncDepth(receive, change, nested)
for (let i = depth; i < change + this.lookahead; i++) {
const key = this.deriveChange(i);
await this.saveKey(key);
await this.saveKey(b, key);
}
this.change = this.deriveChange(change - 1);
@ -693,7 +692,7 @@ Account.prototype.syncDepth = async function syncDepth(receive, change, nested)
for (let i = depth; i < nested + this.lookahead; i++) {
const key = this.deriveNested(i);
await this.saveKey(key);
await this.saveKey(b, key);
}
this.nested = this.deriveNested(nested - 1);
@ -704,7 +703,7 @@ Account.prototype.syncDepth = async function syncDepth(receive, change, nested)
}
if (derived)
this.save();
this.save(b);
return result;
};
@ -715,13 +714,9 @@ Account.prototype.syncDepth = async function syncDepth(receive, change, nested)
* @returns {Promise}
*/
Account.prototype.setLookahead = async function setLookahead(lookahead) {
if (lookahead === this.lookahead) {
this.wdb.logger.warning(
'Lookahead is not changing for: %s/%s.',
this.id, this.name);
Account.prototype.setLookahead = async function setLookahead(b, lookahead) {
if (lookahead === this.lookahead)
return;
}
if (lookahead < this.lookahead) {
const diff = this.lookahead - lookahead;
@ -739,7 +734,7 @@ Account.prototype.setLookahead = async function setLookahead(lookahead) {
this.lookahead = lookahead;
this.save();
this.save(b);
return;
}
@ -750,7 +745,7 @@ Account.prototype.setLookahead = async function setLookahead(lookahead) {
for (let i = depth; i < target; i++) {
const key = this.deriveReceive(i);
await this.saveKey(key);
await this.saveKey(b, key);
}
}
@ -760,7 +755,7 @@ Account.prototype.setLookahead = async function setLookahead(lookahead) {
for (let i = depth; i < target; i++) {
const key = this.deriveChange(i);
await this.saveKey(key);
await this.saveKey(b, key);
}
}
@ -770,12 +765,12 @@ Account.prototype.setLookahead = async function setLookahead(lookahead) {
for (let i = depth; i < target; i++) {
const key = this.deriveNested(i);
await this.saveKey(key);
await this.saveKey(b, key);
}
}
this.lookahead = lookahead;
this.save();
this.save(b);
};
/**

View File

@ -305,12 +305,12 @@ HTTPServer.prototype.initRouter = function initRouter() {
// Change passphrase
this.post('/:id/passphrase', async (req, res) => {
const valid = req.valid();
const passphrase = valid.str('passphrase');
const old = valid.str('old');
const new_ = valid.str('new');
enforce(old || new_, 'Passphrase is required.');
await req.wallet.setPassphrase(old, new_);
await req.wallet.setPassphrase(passphrase, old);
res.send(200, { success: true });
});
@ -416,7 +416,7 @@ HTTPServer.prototype.initRouter = function initRouter() {
const details = await req.wallet.getDetails(tx.hash('hex'));
res.send(200, details.toJSON());
res.send(200, details.toJSON(this.network));
});
// Create TX
@ -790,7 +790,7 @@ HTTPServer.prototype.initRouter = function initRouter() {
const details = await req.wallet.toDetails(tx);
res.send(200, details.toJSON());
res.send(200, details.toJSON(this.network));
});
// Resend
@ -813,38 +813,38 @@ HTTPServer.prototype.initSockets = function initSockets() {
this.handleSocket(socket);
});
this.walletdb.on('tx', (id, tx, details) => {
const json = details.toJSON();
this.to(`w:${id}`, 'wallet tx', json);
this.walletdb.on('tx', (w, tx, details) => {
const json = details.toJSON(this.network);
this.to(`w:${w.id}`, 'wallet tx', json);
});
this.walletdb.on('confirmed', (id, tx, details) => {
const json = details.toJSON();
this.to(`w:${id}`, 'wallet confirmed', json);
this.walletdb.on('confirmed', (w, tx, details) => {
const json = details.toJSON(this.network);
this.to(`w:${w.id}`, 'wallet confirmed', json);
});
this.walletdb.on('unconfirmed', (id, tx, details) => {
const json = details.toJSON();
this.to(`w:${id}`, 'wallet unconfirmed', json);
this.walletdb.on('unconfirmed', (w, tx, details) => {
const json = details.toJSON(this.network);
this.to(`w:${w.id}`, 'wallet unconfirmed', json);
});
this.walletdb.on('conflict', (id, tx, details) => {
const json = details.toJSON();
this.to(`w:${id}`, 'wallet conflict', json);
this.walletdb.on('conflict', (w, tx, details) => {
const json = details.toJSON(this.network);
this.to(`w:${w.id}`, 'wallet conflict', json);
});
this.walletdb.on('balance', (id, balance) => {
this.walletdb.on('balance', (w, balance) => {
const json = balance.toJSON();
this.to(`w:${id}`, 'wallet balance', json);
this.to(`w:${w.id}`, 'wallet balance', json);
});
this.walletdb.on('address', (id, receive) => {
this.walletdb.on('address', (w, receive) => {
const json = [];
for (const addr of receive)
json.push(addr.toJSON());
this.to(`w:${id}`, 'wallet address', json);
this.to(`w:${w.id}`, 'wallet address', json);
});
};

View File

@ -95,13 +95,8 @@ layouts.walletdb = {
layouts.txdb = {
binary: false,
prefix: function prefix(wid, key) {
assert(typeof key === 'string');
return 't' + pad32(wid) + key;
},
pre: function pre(key) {
assert(typeof key === 'string');
return parseInt(key.slice(1, 11), 10);
prefix: function prefix(wid) {
return 't' + pad32(wid);
},
R: 'R',
hi: function hi(ch, hash, index) {
@ -110,8 +105,7 @@ layouts.txdb = {
},
hii: function hii(key) {
assert(typeof key === 'string');
key = key.slice(12);
return [key.slice(0, 64), parseInt(key.slice(64), 10)];
return [key.slice(1, 65), parseInt(key.slice(65), 10)];
},
ih: function ih(ch, index, hash) {
assert(typeof hash === 'string');
@ -119,8 +113,7 @@ layouts.txdb = {
},
ihh: function ihh(key) {
assert(typeof key === 'string');
key = key.slice(12);
return [parseInt(key.slice(0, 10), 10), key.slice(10)];
return [parseInt(key.slice(1, 11), 10), key.slice(11)];
},
iih: function iih(ch, index, num, hash) {
assert(typeof hash === 'string');
@ -128,11 +121,10 @@ layouts.txdb = {
},
iihh: function iihh(key) {
assert(typeof key === 'string');
key = key.slice(12);
return [
parseInt(key.slice(0, 10), 10),
parseInt(key.slice(10, 20), 10),
key.slice(20)
parseInt(key.slice(1, 11), 10),
parseInt(key.slice(11, 21), 10),
key.slice(21)
];
},
ihi: function ihi(ch, index, hash, num) {
@ -141,11 +133,10 @@ layouts.txdb = {
},
ihii: function ihii(key) {
assert(typeof key === 'string');
key = key.slice(12);
return [
parseInt(key.slice(0, 10), 10),
key.slice(10, 74),
parseInt(key.slice(74), 10)
parseInt(key.slice(1, 11), 10),
key.slice(11, 75),
parseInt(key.slice(75), 10)
];
},
ha: function ha(ch, hash) {
@ -154,8 +145,7 @@ layouts.txdb = {
},
haa: function haa(key) {
assert(typeof key === 'string');
key = key.slice(12);
return key;
return key.slice(1);
},
t: function t(hash) {
return this.ha('t', hash);
@ -235,15 +225,11 @@ layouts.txdb = {
Cc: function Cc(key) {
return this.ihii(key);
},
r: function r(hash) {
return this.ha('r', hash);
},
b: function b(height) {
return 'b' + pad32(height);
},
bb: function bb(key) {
assert(typeof key === 'string');
key = key.slice(12);
return parseInt(key.slice(0), 10);
return parseInt(key.slice(1), 10);
}
};

View File

@ -199,20 +199,13 @@ layouts.walletdb = {
layouts.txdb = {
binary: true,
prefix: function prefix(wid, key) {
prefix: function prefix(wid) {
assert(typeof wid === 'number');
assert(Buffer.isBuffer(key));
const out = Buffer.allocUnsafe(5 + key.length);
const out = Buffer.allocUnsafe(5);
out[0] = 0x74;
out.writeUInt32BE(wid, 1);
key.copy(out, 5);
return out;
},
pre: function pre(key) {
assert(Buffer.isBuffer(key));
assert(key.length >= 5);
return key.readUInt32BE(1, true);
},
R: Buffer.from([0x52]),
hi: function hi(ch, hash, index) {
assert(typeof hash === 'string');
@ -225,9 +218,8 @@ layouts.txdb = {
},
hii: function hii(key) {
assert(Buffer.isBuffer(key));
assert(key.length - 5 === 37);
key = key.slice(6);
return [key.toString('hex', 0, 32), key.readUInt32BE(32, true)];
assert(key.length === 37);
return [key.toString('hex', 1, 33), key.readUInt32BE(33, true)];
},
ih: function ih(ch, index, hash) {
assert(typeof index === 'number');
@ -240,9 +232,8 @@ layouts.txdb = {
},
ihh: function ihh(key) {
assert(Buffer.isBuffer(key));
assert(key.length - 5 === 37);
key = key.slice(6);
return [key.readUInt32BE(0, true), key.toString('hex', 4, 36)];
assert(key.length === 37);
return [key.readUInt32BE(1, true), key.toString('hex', 5, 37)];
},
iih: function iih(ch, index, num, hash) {
assert(typeof index === 'number');
@ -257,12 +248,11 @@ layouts.txdb = {
},
iihh: function iihh(key) {
assert(Buffer.isBuffer(key));
assert(key.length - 5 === 41);
key = key.slice(6);
assert(key.length === 41);
return [
key.readUInt32BE(0, true),
key.readUInt32BE(4, true),
key.toString('hex', 8, 40)
key.readUInt32BE(1, true),
key.readUInt32BE(5, true),
key.toString('hex', 9, 41)
];
},
ihi: function ihi(ch, index, hash, num) {
@ -278,12 +268,11 @@ layouts.txdb = {
},
ihii: function ihii(key) {
assert(Buffer.isBuffer(key));
assert(key.length - 5 === 41);
key = key.slice(6);
assert(key.length === 41);
return [
key.readUInt32BE(0, true),
key.toString('hex', 4, 36),
key.readUInt32BE(36, true)
key.readUInt32BE(1, true),
key.toString('hex', 5, 37),
key.readUInt32BE(37, true)
];
},
ha: function ha(ch, hash) {
@ -295,9 +284,8 @@ layouts.txdb = {
},
haa: function haa(key) {
assert(Buffer.isBuffer(key));
assert(key.length - 5 === 33);
key = key.slice(6);
return key.toString('hex', 0);
assert(key.length === 33);
return key.toString('hex', 1, 33);
},
t: function t(hash) {
return this.ha(0x74, hash);
@ -371,9 +359,6 @@ layouts.txdb = {
Cc: function Cc(key) {
return this.ihii(key);
},
r: function r(hash) {
return this.ha(0x72, hash);
},
b: function b(height) {
assert(typeof height === 'number');
const key = Buffer.allocUnsafe(5);
@ -383,8 +368,7 @@ layouts.txdb = {
},
bb: function bb(key) {
assert(Buffer.isBuffer(key));
assert(key.length - 5 === 5);
key = key.slice(6);
return key.readUInt32BE(0, true);
assert(key.length === 5);
return key.readUInt32BE(1, true);
}
};

View File

@ -316,7 +316,7 @@ RPC.prototype.encryptWallet = async function encryptWallet(args, help) {
throw new RPCError(errs.MISC_ERROR, 'encryptwallet "passphrase"');
try {
await wallet.setPassphrase(passphrase);
await wallet.encrypt(passphrase);
} catch (e) {
throw new RPCError(errs.WALLET_ENCRYPTION_FAILED, 'Encryption failed.');
}
@ -1432,15 +1432,15 @@ RPC.prototype.walletPassphraseChange = async function walletPassphraseChange(arg
const valid = new Validator([args]);
const old = valid.str(0, '');
const new_ = valid.str(1, '');
const passphrase = valid.str(1, '');
if (!wallet.master.encrypted)
throw new RPCError(errs.WALLET_WRONG_ENC_STATE, 'Wallet is not encrypted.');
if (old.length < 1 || new_.length < 1)
if (old.length < 1 || passphrase.length < 1)
throw new RPCError(errs.INVALID_PARAMETER, 'Invalid parameter');
await wallet.setPassphrase(old, new_);
await wallet.setPassphrase(passphrase, old);
return null;
};

File diff suppressed because it is too large Load Diff

View File

@ -30,7 +30,6 @@ const HD = require('../hd/hd');
const Output = require('../primitives/output');
const Account = require('./account');
const MasterKey = require('./masterkey');
const LRU = require('../utils/lru');
const policy = require('../protocol/policy');
const consensus = require('../protocol/consensus');
const Mnemonic = HD.Mnemonic;
@ -70,15 +69,11 @@ function Wallet(wdb, options) {
assert(wdb, 'WDB required.');
this.wdb = wdb;
this.db = wdb.db;
this.network = wdb.network;
this.logger = wdb.logger;
this.readLock = new MappedLock();
this.writeLock = new Lock();
this.fundLock = new Lock();
this.indexCache = new LRU(1000);
this.accountCache = new LRU(1000);
this.pathCache = new LRU(1000);
this.current = null;
this.wid = 0;
this.id = null;
@ -89,7 +84,7 @@ function Wallet(wdb, options) {
this.tokenDepth = 0;
this.master = new MasterKey();
this.txdb = new TXDB(this);
this.txdb = new TXDB(this.wdb);
this.account = null;
if (options)
@ -207,7 +202,7 @@ Wallet.prototype.init = async function init(options) {
this.logger.info('Wallet initialized (%s).', this.id);
await this.txdb.open();
await this.txdb.open(this);
};
/**
@ -227,7 +222,7 @@ Wallet.prototype.open = async function open() {
this.logger.info('Wallet opened (%s).', this.id);
await this.txdb.open();
await this.txdb.open(this);
};
/**
@ -239,9 +234,7 @@ Wallet.prototype.destroy = async function destroy() {
const unlock1 = await this.writeLock.lock();
const unlock2 = await this.fundLock.lock();
try {
this.wdb.unregister(this);
await this.master.destroy();
this.readLock.destroy();
this.writeLock.destroy();
this.fundLock.destroy();
} finally {
@ -276,30 +269,14 @@ Wallet.prototype.addSharedKey = async function addSharedKey(acct, key) {
*/
Wallet.prototype._addSharedKey = async function _addSharedKey(acct, key) {
if (!key) {
key = acct;
acct = null;
}
if (acct == null)
acct = 0;
const account = await this.getAccount(acct);
if (!account)
throw new Error('Account not found.');
this.start();
let result;
try {
result = await account.addSharedKey(key);
} catch (e) {
this.drop();
throw e;
}
await this.commit();
const b = this.db.batch();
const result = await account.addSharedKey(b, key);
await b.write();
return result;
};
@ -329,47 +306,30 @@ Wallet.prototype.removeSharedKey = async function removeSharedKey(acct, key) {
*/
Wallet.prototype._removeSharedKey = async function _removeSharedKey(acct, key) {
if (!key) {
key = acct;
acct = null;
}
if (acct == null)
acct = 0;
const account = await this.getAccount(acct);
if (!account)
throw new Error('Account not found.');
this.start();
let result;
try {
result = await account.removeSharedKey(key);
} catch (e) {
this.drop();
throw e;
}
await this.commit();
const b = this.db.batch();
const result = await account.removeSharedKey(b, key);
await b.write();
return result;
};
/**
* Change or set master key's passphrase.
* @param {(String|Buffer)?} old
* @param {String|Buffer} new_
* @param {String|Buffer} passphrase
* @param {String|Buffer} old
* @returns {Promise}
*/
Wallet.prototype.setPassphrase = async function setPassphrase(old, new_) {
if (old)
Wallet.prototype.setPassphrase = async function setPassphrase(passphrase, old) {
if (old != null)
await this.decrypt(old);
if (new_)
await this.encrypt(new_);
await this.encrypt(passphrase);
};
/**
@ -396,22 +356,17 @@ Wallet.prototype.encrypt = async function encrypt(passphrase) {
Wallet.prototype._encrypt = async function _encrypt(passphrase) {
const key = await this.master.encrypt(passphrase, true);
this.start();
const b = this.db.batch();
try {
await this.wdb.encryptKeys(this, key);
} catch (e) {
await this.wdb.encryptKeys(b, this.wid, key);
} finally {
cleanse(key);
this.drop();
throw e;
}
cleanse(key);
this.save(b);
this.save();
await this.commit();
await b.write();
};
/**
@ -438,22 +393,17 @@ Wallet.prototype.decrypt = async function decrypt(passphrase) {
Wallet.prototype._decrypt = async function _decrypt(passphrase) {
const key = await this.master.decrypt(passphrase, true);
this.start();
const b = this.db.batch();
try {
await this.wdb.decryptKeys(this, key);
} catch (e) {
await this.wdb.decryptKeys(b, this.wid, key);
} finally {
cleanse(key);
this.drop();
throw e;
}
cleanse(key);
this.save(b);
this.save();
await this.commit();
await b.write();
};
/**
@ -479,15 +429,16 @@ Wallet.prototype.retoken = async function retoken(passphrase) {
*/
Wallet.prototype._retoken = async function _retoken(passphrase) {
await this.unlock(passphrase);
if (passphrase)
await this.unlock(passphrase);
this.tokenDepth++;
this.tokenDepth += 1;
this.token = this.getToken(this.tokenDepth);
this.start();
this.save();
const b = this.db.batch();
this.save(b);
await this.commit();
await b.write();
return this.token;
};
@ -546,24 +497,11 @@ Wallet.prototype._renameAccount = async function _renameAccount(acct, name) {
if (await this.hasAccount(name))
throw new Error('Account name not available.');
const old = account.name;
const b = this.db.batch();
this.start();
this.wdb.renameAccount(b, account, name);
this.wdb.renameAccount(account, name);
await this.commit();
this.indexCache.remove(old);
const paths = this.pathCache.values();
for (const path of paths) {
if (path.account !== account.accountIndex)
continue;
path.name = name;
}
await b.write();
};
/**
@ -631,7 +569,8 @@ Wallet.prototype.getID = function getID() {
*/
Wallet.prototype.getToken = function getToken(nonce) {
assert(this.master.key, 'Cannot derive token.');
if (!this.master.key)
throw new Error('Cannot derive token.');
const key = this.master.key.derive(44, true);
@ -706,17 +645,11 @@ Wallet.prototype._createAccount = async function _createAccount(options, passphr
keys: options.keys
};
this.start();
const b = this.db.batch();
let account;
try {
account = Account.fromOptions(this.wdb, opt);
account.wallet = this;
await account.init();
} catch (e) {
this.drop();
throw e;
}
const account = Account.fromOptions(this.wdb, opt);
await account.init(b);
this.logger.info('Created account %s/%s/%d.',
account.id,
@ -724,9 +657,9 @@ Wallet.prototype._createAccount = async function _createAccount(options, passphr
account.accountIndex);
this.accountDepth++;
this.save();
this.save(b);
await this.commit();
await b.write();
return account;
};
@ -775,7 +708,11 @@ Wallet.prototype.getAddressHashes = function getAddressHashes(acct) {
*/
Wallet.prototype.getAccountHashes = async function getAccountHashes(acct) {
const index = await this.ensureIndex(acct, true);
const index = await this.getAccountIndex(acct);
if (index === -1)
throw new Error('Account not found.');
return await this.wdb.getAccountHashes(this.wid, index);
};
@ -796,69 +733,51 @@ Wallet.prototype.getAccount = async function getAccount(acct) {
if (index === -1)
return null;
const unlock = await this.readLock.lock(index);
try {
return await this._getAccount(index);
} finally {
unlock();
}
};
/**
* Retrieve an account from the database without a lock.
* @param {Number} index
* @returns {Promise} - Returns {@link Account}.
*/
Wallet.prototype._getAccount = async function _getAccount(index) {
const cache = this.accountCache.get(index);
if (cache)
return cache;
const account = await this.wdb.getAccount(this.wid, index);
if (!account)
return null;
account.wallet = this;
account.wid = this.wid;
account.id = this.id;
account.watchOnly = this.watchOnly;
await account.open();
this.accountCache.set(index, account);
return account;
};
/**
* Lookup the corresponding account name's index.
* @param {WalletID} wid
* @param {String|Number} name - Account name/index.
* @param {String|Number} acct - Account name/index.
* @returns {Promise} - Returns Number.
*/
Wallet.prototype.getAccountIndex = async function getAccountIndex(name) {
if (name == null)
Wallet.prototype.getAccountIndex = function getAccountIndex(acct) {
if (acct == null)
return -1;
if (typeof name === 'number')
return name;
if (typeof acct === 'number')
return acct;
const cache = this.indexCache.get(name);
return this.wdb.getAccountIndex(this.wid, acct);
};
if (cache != null)
return cache;
/**
* Lookup the corresponding account name's index.
* @param {String|Number} acct - Account name/index.
* @returns {Promise} - Returns Number.
* @throws on non-existent account
*/
const index = await this.wdb.getAccountIndex(this.wid, name);
Wallet.prototype.ensureIndex = async function ensureIndex(acct) {
if (acct == null || acct === -1)
return -1;
const index = await this.getAccountIndex(acct);
if (index === -1)
return -1;
this.indexCache.set(name, index);
throw new Error('Account not found.');
return index;
};
@ -874,11 +793,6 @@ Wallet.prototype.getAccountName = async function getAccountName(index) {
if (typeof index === 'string')
return index;
const account = this.accountCache.get(index);
if (account)
return account.name;
return await this.wdb.getAccountName(this.wid, index);
};
@ -894,9 +808,6 @@ Wallet.prototype.hasAccount = async function hasAccount(acct) {
if (index === -1)
return false;
if (this.accountCache.has(index))
return true;
return await this.db.hasAccount(this.wid, index);
};
@ -906,7 +817,7 @@ Wallet.prototype.hasAccount = async function hasAccount(acct) {
* @returns {Promise} - Returns {@link WalletKey}.
*/
Wallet.prototype.createReceive = function createReceive(acct) {
Wallet.prototype.createReceive = function createReceive(acct = 0) {
return this.createKey(acct, 0);
};
@ -916,7 +827,7 @@ Wallet.prototype.createReceive = function createReceive(acct) {
* @returns {Promise} - Returns {@link WalletKey}.
*/
Wallet.prototype.createChange = function createChange(acct) {
Wallet.prototype.createChange = function createChange(acct = 0) {
return this.createKey(acct, 1);
};
@ -926,7 +837,7 @@ Wallet.prototype.createChange = function createChange(acct) {
* @returns {Promise} - Returns {@link WalletKey}.
*/
Wallet.prototype.createNested = function createNested(acct) {
Wallet.prototype.createNested = function createNested(acct = 0) {
return this.createKey(acct, 2);
};
@ -955,32 +866,16 @@ Wallet.prototype.createKey = async function createKey(acct, branch) {
*/
Wallet.prototype._createKey = async function _createKey(acct, branch) {
if (branch == null) {
branch = acct;
acct = null;
}
if (acct == null)
acct = 0;
const account = await this.getAccount(acct);
if (!account)
throw new Error('Account not found.');
this.start();
const b = this.db.batch();
const key = await account.createKey(b, branch);
await b.write();
let result;
try {
result = await account.createKey(branch);
} catch (e) {
this.drop();
throw e;
}
await this.commit();
return result;
return key;
};
/**
@ -989,44 +884,8 @@ Wallet.prototype._createKey = async function _createKey(acct, branch) {
* @returns {Promise}
*/
Wallet.prototype.save = function save() {
return this.wdb.save(this);
};
/**
* Start batch.
* @private
*/
Wallet.prototype.start = function start() {
return this.wdb.start(this);
};
/**
* Drop batch.
* @private
*/
Wallet.prototype.drop = function drop() {
return this.wdb.drop(this);
};
/**
* Clear batch.
* @private
*/
Wallet.prototype.clear = function clear() {
return this.wdb.clear(this);
};
/**
* Save batch.
* @returns {Promise}
*/
Wallet.prototype.commit = function commit() {
return this.wdb.commit(this);
Wallet.prototype.save = function save(b) {
return this.wdb.save(b, this);
};
/**
@ -1048,18 +907,8 @@ Wallet.prototype.hasAddress = async function hasAddress(address) {
*/
Wallet.prototype.getPath = async function getPath(address) {
const path = await this.readPath(address);
if (!path)
return null;
path.name = await this.getAccountName(path.account);
assert(path.name);
this.pathCache.set(path.hash, path);
return path;
const hash = Address.getHash(address, 'hex');
return this.wdb.getPath(this.wid, this.id, hash);
};
/**
@ -1071,19 +920,7 @@ Wallet.prototype.getPath = async function getPath(address) {
Wallet.prototype.readPath = async function readPath(address) {
const hash = Address.getHash(address, 'hex');
const cache = this.pathCache.get(hash);
if (cache)
return cache;
const path = await this.wdb.getPath(this.wid, hash);
if (!path)
return null;
path.id = this.id;
return path;
return this.wdb.readPath(this.wid, this.id, hash);
};
/**
@ -1094,10 +931,6 @@ Wallet.prototype.readPath = async function readPath(address) {
Wallet.prototype.hasPath = async function hasPath(address) {
const hash = Address.getHash(address, 'hex');
if (this.pathCache.has(hash))
return true;
return await this.wdb.hasPath(this.wid, hash);
};
@ -1120,8 +953,6 @@ Wallet.prototype.getPaths = async function getPaths(acct) {
assert(path.name);
this.pathCache.set(path.hash, path);
result.push(path);
}
@ -1135,7 +966,11 @@ Wallet.prototype.getPaths = async function getPaths(acct) {
*/
Wallet.prototype.getAccountPaths = async function getAccountPaths(acct) {
const index = await this.ensureIndex(acct, true);
const index = await this.getAccountIndex(acct);
if (index === -1)
throw new Error('Account not found.');
const hashes = await this.getAccountHashes(index);
const name = await this.getAccountName(acct);
@ -1151,8 +986,6 @@ Wallet.prototype.getAccountPaths = async function getAccountPaths(acct) {
path.name = name;
this.pathCache.set(path.hash, path);
result.push(path);
}
@ -1187,15 +1020,6 @@ Wallet.prototype.importKey = async function importKey(acct, ring, passphrase) {
*/
Wallet.prototype._importKey = async function _importKey(acct, ring, passphrase) {
if (acct && typeof acct === 'object') {
passphrase = ring;
ring = acct;
acct = null;
}
if (acct == null)
acct = 0;
assert(ring.network === this.network,
'Network mismatch for key.');
@ -1231,16 +1055,9 @@ Wallet.prototype._importKey = async function _importKey(acct, ring, passphrase)
path.encrypted = true;
}
this.start();
try {
await account.savePath(path);
} catch (e) {
this.drop();
throw e;
}
await this.commit();
const b = this.db.batch();
await account.savePath(b, path);
await b.write();
};
/**
@ -1271,14 +1088,6 @@ Wallet.prototype.importAddress = async function importAddress(acct, address) {
*/
Wallet.prototype._importAddress = async function _importAddress(acct, address) {
if (!address) {
address = acct;
acct = null;
}
if (acct == null)
acct = 0;
assert(address.network === this.network,
'Network mismatch for address.');
@ -1298,16 +1107,9 @@ Wallet.prototype._importAddress = async function _importAddress(acct, address) {
const path = Path.fromAddress(account, address);
this.start();
try {
await account.savePath(path);
} catch (e) {
this.drop();
throw e;
}
await this.commit();
const b = this.db.batch();
await account.savePath(b, path);
await b.write();
};
/**
@ -1881,29 +1683,14 @@ Wallet.prototype.setLookahead = async function setLookahead(acct, lookahead) {
*/
Wallet.prototype._setLookahead = async function _setLookahead(acct, lookahead) {
if (lookahead == null) {
lookahead = acct;
acct = null;
}
if (acct == null)
acct = 0;
const account = await this.getAccount(acct);
if (!account)
throw new Error('Account not found.');
this.start();
try {
await account.setLookahead(lookahead);
} catch (e) {
this.drop();
throw e;
}
await this.commit();
const b = this.db.batch();
await account.setLookahead(b, lookahead);
await b.write();
};
/**
@ -1933,6 +1720,7 @@ Wallet.prototype.syncOutputDepth = async function syncOutputDepth(tx) {
}
const derived = [];
const b = this.db.batch();
for (const [acct, paths] of map) {
let receive = -1;
@ -1963,33 +1751,17 @@ Wallet.prototype.syncOutputDepth = async function syncOutputDepth(tx) {
const account = await this.getAccount(acct);
assert(account);
const ring = await account.syncDepth(receive, change, nested);
const ring = await account.syncDepth(b, receive, change, nested);
if (ring)
derived.push(ring);
}
await b.write();
return derived;
};
/**
* Get a redeem script or witness script by hash.
* @param {Hash} hash - Can be a ripemd160 or a sha256.
* @returns {Script}
*/
Wallet.prototype.getRedeem = async function getRedeem(hash) {
if (typeof hash === 'string')
hash = Buffer.from(hash, 'hex');
const ring = await this.getKey(hash.toString('hex'));
if (!ring)
return null;
return ring.getRedeem(hash);
};
/**
* Build input scripts templates for a transaction (does not
* sign, only creates signature slots). Only builds scripts
@ -2021,7 +1793,7 @@ Wallet.prototype.sign = async function sign(mtx, passphrase) {
const rings = await this.deriveInputs(mtx);
return await mtx.signAsync(rings, Script.hashType.ALL, this.wdb.workers);
return mtx.signAsync(rings, Script.hashType.ALL, this.wdb.workers);
};
/**
@ -2128,23 +1900,14 @@ Wallet.prototype.add = async function add(tx, block) {
*/
Wallet.prototype._add = async function _add(tx, block) {
this.txdb.start();
const details = await this.txdb.add(tx, block);
let details, derived;
try {
details = await this.txdb._add(tx, block);
if (details)
derived = await this.syncOutputDepth(tx);
} catch (e) {
this.txdb.drop();
throw e;
}
await this.txdb.commit();
if (derived && derived.length > 0) {
this.wdb.emit('address', this.id, derived);
this.emit('address', derived);
if (details) {
const derived = await this.syncOutputDepth(tx);
if (derived.length > 0) {
this.wdb.emit('address', this.id, derived);
this.emit('address', derived);
}
}
return details;
@ -2375,10 +2138,6 @@ Wallet.prototype.getBalance = async function getBalance(acct) {
*/
Wallet.prototype.getRange = async function getRange(acct, options) {
if (acct && typeof acct === 'object') {
options = acct;
acct = null;
}
const account = await this.ensureIndex(acct);
return await this.txdb.getRange(account, options);
};
@ -2395,29 +2154,6 @@ Wallet.prototype.getLast = async function getLast(acct, limit) {
return await this.txdb.getLast(account, limit);
};
/**
* Resolve account index.
* @private
* @param {(Number|String)?} acct
* @param {Function} errback - Returns [Error].
* @returns {Promise}
*/
Wallet.prototype.ensureIndex = async function ensureIndex(acct, enforce) {
if (acct == null) {
if (enforce)
throw new Error('No account provided.');
return null;
}
const index = await this.getAccountIndex(acct);
if (index === -1)
throw new Error('Account not found.');
return index;
};
/**
* Get current receive address.
* @param {String?} enc - `"base58"` or `null`.

View File

@ -184,8 +184,10 @@ WalletDB.prototype._close = async function _close() {
if (this.http && this.options.listen)
await this.http.close();
for (const wallet of this.wallets.values())
for (const wallet of this.wallets.values()) {
await wallet.destroy();
this.unregister(wallet);
}
await this.db.close();
@ -303,14 +305,14 @@ WalletDB.prototype.disconnect = async function disconnect() {
*/
WalletDB.prototype.init = async function init() {
const state = await this.getState();
const cache = await this.getState();
if (state) {
this.state = state;
if (cache) {
this.state = cache;
return;
}
const batch = this.db.batch();
const b = this.db.batch();
let tip = null;
@ -320,27 +322,27 @@ WalletDB.prototype.init = async function init() {
for (let height = 0; height < hashes.length; height++) {
const hash = hashes[height];
const meta = new BlockMeta(hash, height);
batch.put(layout.h(height), meta.toHash());
b.put(layout.h(height), meta.toHash());
tip = meta;
}
} else {
tip = new BlockMeta(this.network.genesis.hash, 0);
batch.put(layout.h(0), tip.toHash());
b.put(layout.h(0), tip.toHash());
}
assert(tip);
const pending = this.state.clone();
pending.startHeight = tip.height;
pending.startHash = tip.hash;
pending.height = tip.height;
pending.marked = false;
const state = this.state.clone();
state.startHeight = tip.height;
state.startHash = tip.hash;
state.height = tip.height;
state.marked = false;
batch.put(layout.R, pending.toRaw());
b.put(layout.R, state.toRaw());
await batch.write();
await b.write();
this.state = pending;
this.state = state;
};
/**
@ -587,7 +589,7 @@ WalletDB.prototype.wipe = async function wipe() {
lte: Buffer.from([0xff])
});
const batch = this.db.batch();
const b = this.db.batch();
let total = 0;
@ -600,7 +602,7 @@ WalletDB.prototype.wipe = async function wipe() {
case 0x6f: // o
case 0x68: // h
case 0x52: // R
batch.del(key);
b.del(key);
total += 1;
break;
}
@ -608,7 +610,7 @@ WalletDB.prototype.wipe = async function wipe() {
this.logger.warning('Wiped %d txdb records.', total);
await batch.write();
await b.write();
};
/**
@ -646,83 +648,6 @@ WalletDB.prototype.getDepth = async function getDepth() {
return depth + 1;
};
/**
* Start batch.
* @private
* @param {WalletID} wid
*/
WalletDB.prototype.start = function start(wallet) {
assert(!wallet.current, 'WDB: Batch already started.');
wallet.current = this.db.batch();
wallet.accountCache.start();
wallet.pathCache.start();
return wallet.current;
};
/**
* Drop batch.
* @private
* @param {WalletID} wid
*/
WalletDB.prototype.drop = function drop(wallet) {
const batch = this.batch(wallet);
wallet.current = null;
wallet.accountCache.drop();
wallet.pathCache.drop();
batch.clear();
};
/**
* Clear batch.
* @private
* @param {WalletID} wid
*/
WalletDB.prototype.clear = function clear(wallet) {
const batch = this.batch(wallet);
wallet.accountCache.clear();
wallet.pathCache.clear();
batch.clear();
};
/**
* Get batch.
* @private
* @param {WalletID} wid
* @returns {Leveldown.Batch}
*/
WalletDB.prototype.batch = function batch(wallet) {
assert(wallet.current, 'WDB: Batch does not exist.');
return wallet.current;
};
/**
* Save batch.
* @private
* @param {WalletID} wid
* @returns {Promise}
*/
WalletDB.prototype.commit = async function commit(wallet) {
const batch = this.batch(wallet);
try {
await batch.write();
} catch (e) {
wallet.current = null;
wallet.accountCache.drop();
wallet.pathCache.drop();
throw e;
}
wallet.current = null;
wallet.accountCache.commit();
wallet.pathCache.commit();
};
/**
* Test the bloom filter against a tx or address hash.
* @private
@ -793,7 +718,7 @@ WalletDB.prototype.unregister = function unregister(wallet) {
* @returns {Promise} - Returns {WalletID}.
*/
WalletDB.prototype.getWalletID = async function getWalletID(id) {
WalletDB.prototype.getWID = async function getWID(id) {
if (!id)
return null;
@ -817,7 +742,7 @@ WalletDB.prototype.getWalletID = async function getWalletID(id) {
*/
WalletDB.prototype.get = async function get(id) {
const wid = await this.getWalletID(id);
const wid = await this.getWID(id);
if (!wid)
return null;
@ -863,13 +788,12 @@ WalletDB.prototype._get = async function _get(wid) {
* @param {Wallet} wallet
*/
WalletDB.prototype.save = function save(wallet) {
WalletDB.prototype.save = function save(b, wallet) {
const wid = wallet.wid;
const id = wallet.id;
const batch = this.batch(wallet);
batch.put(layout.w(wid), wallet.toRaw());
batch.put(layout.l(id), U32(wid));
b.put(layout.w(wid), wallet.toRaw());
b.put(layout.l(id), U32(wid));
};
/**
@ -897,27 +821,23 @@ WalletDB.prototype.rename = async function rename(wallet, id) {
*/
WalletDB.prototype._rename = async function _rename(wallet, id) {
const old = wallet.id;
if (!common.isName(id))
throw new Error('WDB: Bad wallet ID.');
if (await this.has(id))
throw new Error('WDB: ID not available.');
const batch = this.start(wallet);
batch.del(layout.l(old));
const old = wallet.id;
const b = this.db.batch();
b.del(layout.l(old));
wallet.id = id;
wallet.txdb.id = id;
this.save(wallet);
this.save(b, wallet);
await this.commit(wallet);
const paths = wallet.pathCache.values();
for (const path of paths)
path.id = id;
await b.write();
};
/**
@ -926,16 +846,13 @@ WalletDB.prototype._rename = async function _rename(wallet, id) {
* @param {String} name
*/
WalletDB.prototype.renameAccount = function renameAccount(account, name) {
const wallet = account.wallet;
const batch = this.batch(wallet);
WalletDB.prototype.renameAccount = function renameAccount(b, account, name) {
// Remove old wid/name->account index.
batch.del(layout.i(account.wid, account.name));
b.del(layout.i(account.wid, account.name));
account.name = name;
this.saveAccount(account);
this.saveAccount(b, account);
};
/**
@ -1018,7 +935,7 @@ WalletDB.prototype._create = async function _create(options) {
*/
WalletDB.prototype.has = async function has(id) {
const wid = await this.getWalletID(id);
const wid = await this.getWID(id);
return wid != null;
};
@ -1106,23 +1023,19 @@ WalletDB.prototype.getAccountName = async function getAccountName(wid, index) {
* @returns {Promise}
*/
WalletDB.prototype.saveAccount = function saveAccount(account) {
WalletDB.prototype.saveAccount = function saveAccount(b, account) {
const wid = account.wid;
const wallet = account.wallet;
const index = account.accountIndex;
const name = account.name;
const batch = this.batch(wallet);
// Account data
batch.put(layout.a(wid, index), account.toRaw());
b.put(layout.a(wid, index), account.toRaw());
// Name->Index lookups
batch.put(layout.i(wid, name), U32(index));
b.put(layout.i(wid, name), U32(index));
// Index->Name lookups
batch.put(layout.n(wid, index), Buffer.from(name, 'ascii'));
wallet.accountCache.push(index, account);
b.put(layout.n(wid, index), Buffer.from(name, 'ascii'));
};
/**
@ -1143,8 +1056,8 @@ WalletDB.prototype.hasAccount = function hasAccount(wid, index) {
* @returns {Promise}
*/
WalletDB.prototype.saveKey = function saveKey(wallet, ring) {
return this.savePath(wallet, ring.toPath());
WalletDB.prototype.saveKey = function saveKey(b, wid, ring) {
return this.savePath(b, wid, ring.toPath());
};
/**
@ -1160,21 +1073,15 @@ WalletDB.prototype.saveKey = function saveKey(wallet, ring) {
* @returns {Promise}
*/
WalletDB.prototype.savePath = async function savePath(wallet, path) {
const wid = wallet.wid;
const hash = path.hash;
const batch = this.batch(wallet);
wallet.pathCache.push(hash, path);
WalletDB.prototype.savePath = async function savePath(b, wid, path) {
// Address Hash -> Wallet Map
await this.addPathMap(wallet, hash);
await this.addPathMap(b, path.hash, wid);
// Wallet ID + Address Hash -> Path Data
batch.put(layout.P(wid, hash), path.toRaw());
b.put(layout.P(wid, path.hash), path.toRaw());
// Wallet ID + Account Index + Address Hash -> Dummy
batch.put(layout.r(wid, path.account, hash), null);
b.put(layout.r(wid, path.account, path.hash), null);
};
/**
@ -1184,13 +1091,33 @@ WalletDB.prototype.savePath = async function savePath(wallet, path) {
* @returns {Promise}
*/
WalletDB.prototype.getPath = async function getPath(wid, hash) {
WalletDB.prototype.getPath = async function getPath(wid, id, hash) {
const path = await this.readPath(wid, id, hash);
if (!path)
return null;
path.name = await this.getAccountName(wid, path.account);
assert(path.name);
return path;
};
/**
* Retrieve path by hash.
* @param {WalletID} wid
* @param {Hash} hash
* @returns {Promise}
*/
WalletDB.prototype.readPath = async function readPath(wid, id, hash) {
const data = await this.db.get(layout.P(wid, hash));
if (!data)
return null;
const path = Path.fromRaw(data);
path.id = id;
path.wid = wid;
path.hash = hash;
@ -1313,28 +1240,30 @@ WalletDB.prototype.getWallets = function getWallets() {
* @returns {Promise}
*/
WalletDB.prototype.encryptKeys = async function encryptKeys(wallet, key) {
const wid = wallet.wid;
const paths = await wallet.getPaths();
const batch = this.batch(wallet);
WalletDB.prototype.encryptKeys = async function encryptKeys(b, wid, key) {
const iter = this.db.iterator({
gte: layout.P(wid, encoding.NULL_HASH),
lte: layout.P(wid, encoding.HIGH_HASH),
values: true
});
await iter.each((k, value) => {
const hash = layout.Pp(k);
const path = Path.fromRaw(value);
for (let path of paths) {
if (!path.data)
continue;
return;
assert(!path.encrypted);
const hash = Buffer.from(path.hash, 'hex');
const iv = hash.slice(0, 16);
const bhash = Buffer.from(hash, 'hex');
const iv = bhash.slice(0, 16);
path = path.clone();
path.data = aes.encipher(path.data, key, iv);
path.encrypted = true;
wallet.pathCache.push(path.hash, path);
batch.put(layout.P(wid, path.hash), path.toRaw());
}
b.put(k, path.toRaw());
});
};
/**
@ -1344,28 +1273,30 @@ WalletDB.prototype.encryptKeys = async function encryptKeys(wallet, key) {
* @returns {Promise}
*/
WalletDB.prototype.decryptKeys = async function decryptKeys(wallet, key) {
const wid = wallet.wid;
const paths = await wallet.getPaths();
const batch = this.batch(wallet);
WalletDB.prototype.decryptKeys = async function decryptKeys(b, wid, key) {
const iter = this.db.iterator({
gte: layout.P(wid, encoding.NULL_HASH),
lte: layout.P(wid, encoding.HIGH_HASH),
values: true
});
await iter.each((k, value) => {
const hash = layout.Pp(k);
const path = Path.fromRaw(value);
for (let path of paths) {
if (!path.data)
continue;
return;
assert(path.encrypted);
const hash = Buffer.from(path.hash, 'hex');
const iv = hash.slice(0, 16);
const bhash = Buffer.from(hash, 'hex');
const iv = bhash.slice(0, 16);
path = path.clone();
path.data = aes.decipher(path.data, key, iv);
path.encrypted = false;
wallet.pathCache.push(path.hash, path);
batch.put(layout.P(wid, path.hash), path.toRaw());
}
b.put(k, path.toRaw());
});
};
/**
@ -1500,14 +1431,14 @@ WalletDB.prototype.getState = async function getState() {
*/
WalletDB.prototype.syncState = async function syncState(tip) {
const batch = this.db.batch();
const b = this.db.batch();
const state = this.state.clone();
if (tip.height < state.height) {
// Hashes ahead of our new tip
// that we need to delete.
while (state.height !== tip.height) {
batch.del(layout.h(state.height));
b.del(layout.h(state.height));
state.height -= 1;
}
} else if (tip.height > state.height) {
@ -1522,10 +1453,10 @@ WalletDB.prototype.syncState = async function syncState(tip) {
}
// Save tip and state.
batch.put(layout.h(tip.height), tip.toHash());
batch.put(layout.R, state.toRaw());
b.put(layout.h(tip.height), tip.toHash());
b.put(layout.R, state.toRaw());
await batch.write();
await b.write();
this.state = state;
};
@ -1542,9 +1473,9 @@ WalletDB.prototype.markState = async function markState(block) {
state.startHash = block.hash;
state.marked = true;
const batch = this.db.batch();
batch.put(layout.R, state.toRaw());
await batch.write();
const b = this.db.batch();
b.put(layout.R, state.toRaw());
await b.write();
this.state = state;
};
@ -1571,15 +1502,13 @@ WalletDB.prototype.getMap = async function getMap(key) {
* @param {Number} wid
*/
WalletDB.prototype.addMap = async function addMap(wallet, key) {
const wid = wallet.wid;
const batch = this.batch(wallet);
WalletDB.prototype.addMap = async function addMap(b, key, wid) {
const data = await this.db.get(key);
if (!data) {
const map = new MapRecord();
map.add(wid);
batch.put(key, map.toRaw());
b.put(key, map.toRaw());
return;
}
@ -1592,7 +1521,7 @@ WalletDB.prototype.addMap = async function addMap(wallet, key) {
bw.copy(data, 4, data.length);
bw.writeU32(wid);
batch.put(key, bw.render());
b.put(key, bw.render());
};
/**
@ -1602,9 +1531,7 @@ WalletDB.prototype.addMap = async function addMap(wallet, key) {
* @param {Number} wid
*/
WalletDB.prototype.removeMap = async function removeMap(wallet, key) {
const wid = wallet.wid;
const batch = this.batch(wallet);
WalletDB.prototype.removeMap = async function removeMap(b, key, wid) {
const map = await this.getMap(key);
if (!map)
@ -1614,11 +1541,11 @@ WalletDB.prototype.removeMap = async function removeMap(wallet, key) {
return;
if (map.size === 0) {
batch.del(key);
b.del(key);
return;
}
batch.put(key, map.toRaw());
b.put(key, map.toRaw());
};
/**
@ -1638,9 +1565,9 @@ WalletDB.prototype.getPathMap = function getPathMap(hash) {
* @param {Number} wid
*/
WalletDB.prototype.addPathMap = function addPathMap(wallet, hash) {
WalletDB.prototype.addPathMap = function addPathMap(b, hash, wid) {
this.addHash(hash);
return this.addMap(wallet, layout.p(hash));
return this.addMap(b, layout.p(hash), wid);
};
/**
@ -1650,8 +1577,8 @@ WalletDB.prototype.addPathMap = function addPathMap(wallet, hash) {
* @param {Number} wid
*/
WalletDB.prototype.removePathMap = function removePathMap(wallet, hash) {
return this.removeMap(wallet, layout.p(hash));
WalletDB.prototype.removePathMap = function removePathMap(b, hash, wid) {
return this.removeMap(b, layout.p(hash), wid);
};
/**
@ -1671,8 +1598,8 @@ WalletDB.prototype.getBlockMap = async function getBlockMap(height) {
* @param {Number} wid
*/
WalletDB.prototype.addBlockMap = function addBlockMap(wallet, height) {
return this.addMap(wallet, layout.b(height));
WalletDB.prototype.addBlockMap = function addBlockMap(b, height, wid) {
return this.addMap(b, layout.b(height), wid);
};
/**
@ -1682,8 +1609,8 @@ WalletDB.prototype.addBlockMap = function addBlockMap(wallet, height) {
* @param {Number} wid
*/
WalletDB.prototype.removeBlockMap = function removeBlockMap(wallet, height) {
return this.removeMap(wallet, layout.b(height));
WalletDB.prototype.removeBlockMap = function removeBlockMap(b, height, wid) {
return this.removeMap(b, layout.b(height), wid);
};
/**
@ -1703,8 +1630,8 @@ WalletDB.prototype.getTXMap = function getTXMap(hash) {
* @param {Number} wid
*/
WalletDB.prototype.addTXMap = function addTXMap(wallet, hash) {
return this.addMap(wallet, layout.T(hash));
WalletDB.prototype.addTXMap = function addTXMap(b, hash, wid) {
return this.addMap(b, layout.T(hash), wid);
};
/**
@ -1714,8 +1641,8 @@ WalletDB.prototype.addTXMap = function addTXMap(wallet, hash) {
* @param {Number} wid
*/
WalletDB.prototype.removeTXMap = function removeTXMap(wallet, hash) {
return this.removeMap(wallet, layout.T(hash));
WalletDB.prototype.removeTXMap = function removeTXMap(b, hash, wid) {
return this.removeMap(b, layout.T(hash), wid);
};
/**
@ -1724,7 +1651,7 @@ WalletDB.prototype.removeTXMap = function removeTXMap(wallet, hash) {
* @returns {Promise}
*/
WalletDB.prototype.getOutpointMap = async function getOutpointMap(hash, index) {
WalletDB.prototype.getOutpointMap = function getOutpointMap(hash, index) {
return this.getMap(layout.o(hash, index));
};
@ -1735,9 +1662,9 @@ WalletDB.prototype.getOutpointMap = async function getOutpointMap(hash, index) {
* @param {Number} wid
*/
WalletDB.prototype.addOutpointMap = async function addOutpointMap(wallet, hash, index) {
this.addOutpoint(hash, index);
return this.addMap(wallet, layout.o(hash, index));
WalletDB.prototype.addOutpointMap = async function addOutpointMap(b, hash, index, wid) {
await this.addOutpoint(hash, index);
return this.addMap(b, layout.o(hash, index), wid);
};
/**
@ -1747,8 +1674,8 @@ WalletDB.prototype.addOutpointMap = async function addOutpointMap(wallet, hash,
* @param {Number} wid
*/
WalletDB.prototype.removeOutpointMap = async function removeOutpointMap(wallet, hash, index) {
return this.removeMap(wallet, layout.o(hash, index));
WalletDB.prototype.removeOutpointMap = function removeOutpointMap(b, hash, index, wid) {
return this.removeMap(b, layout.o(hash, index), wid);
};
/**

View File

@ -118,14 +118,14 @@ async function testP2SH(witness, nesting) {
const carol = await wdb.create(options);
const recipient = await wdb.create();
await alice.addSharedKey(bob.account.accountKey);
await alice.addSharedKey(carol.account.accountKey);
await alice.addSharedKey(0, bob.account.accountKey);
await alice.addSharedKey(0, carol.account.accountKey);
await bob.addSharedKey(alice.account.accountKey);
await bob.addSharedKey(carol.account.accountKey);
await bob.addSharedKey(0, alice.account.accountKey);
await bob.addSharedKey(0, carol.account.accountKey);
await carol.addSharedKey(alice.account.accountKey);
await carol.addSharedKey(bob.account.accountKey);
await carol.addSharedKey(0, alice.account.accountKey);
await carol.addSharedKey(0, bob.account.accountKey);
// Our p2sh address
const addr1 = alice.account[receive].getAddress();
@ -262,15 +262,8 @@ describe('Wallet', function() {
it('should create and get wallet', async () => {
const wallet1 = await wdb.create();
await wallet1.destroy();
const wallet2 = await wdb.get(wallet1.id);
assert(wallet1 !== wallet2);
assert(wallet1.master !== wallet2.master);
assert(wallet1.master.key.equals(wallet2.master.key));
assert(wallet1.account.accountKey.equals(wallet2.account.accountKey));
assert(wallet1 === wallet2);
});
it('should sign/verify p2pkh tx', async () => {
@ -295,7 +288,7 @@ describe('Wallet', function() {
const xpriv = HD.PrivateKey.generate();
const key = xpriv.deriveAccount(44, 0).toPublic();
await wallet.addSharedKey(key);
await wallet.addSharedKey(0, key);
const script = Script.fromMultisig(1, 2, [
wallet.account.receive.getPublicKey(),
@ -684,7 +677,7 @@ describe('Wallet', function() {
assert.strictEqual(t2.getSize(), 521);
assert.strictEqual(t2.getVirtualSize(), 521);
let balance;
let balance = null;
bob.once('balance', (b) => {
balance = b;
});
@ -927,6 +920,7 @@ describe('Wallet', function() {
passphrase: 'foo'
});
await wallet.destroy();
wdb.unregister(wallet);
}
const wallet = await wdb.get('foobar');
@ -1156,7 +1150,7 @@ describe('Wallet', function() {
it('should get range of txs', async () => {
const wallet = currentWallet;
const txs = await wallet.getRange({
const txs = await wallet.getRange(null, {
start: util.now() - 1000
});
assert.strictEqual(txs.length, 2);