walletdb: remove global methods. in-memory balance.

This commit is contained in:
Christopher Jeffrey 2016-08-14 16:31:12 -07:00
parent 9a1ba962fd
commit 1e98ce25d9
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
4 changed files with 161 additions and 392 deletions

View File

@ -298,7 +298,7 @@ Fullnode.prototype._open = function open(callback) {
},
function(next) {
if (self.options.noScan) {
self.walletdb.writeTip(self.chain.tip.hash, next);
self.walletdb.setTip(self.chain.tip.hash, 0, next);
return next();
}
// Always rescan to make sure we didn't miss anything:

View File

@ -48,12 +48,39 @@ function TXDB(wallet) {
this.logger = wallet.db.logger;
this.network = wallet.db.network;
this.options = wallet.db.options;
this.locker = new bcoin.locker(this);
this.current = null;
this.coinCache = new bcoin.lru(10000, 1);
this.balance = new Balance();
this.current = null;
this.balance = null;
}
/**
* Open TXDB.
* @param {Function} callback
*/
TXDB.prototype.open = function open(callback) {
var self = this;
this.getBalance(function(err, balance) {
if (err)
return callback(err);
self.logger.info('TXDB loaded for %s.', self.wallet.id);
self.logger.info(
'Balance: unconfirmed=%s confirmed=%s total=%s.',
utils.btc(balance.unconfirmed),
utils.btc(balance.confirmed),
utils.btc(balance.total));
self.balance = balance;
return callback();
});
};
/**
* Compile wallet prefix.
* @param {String} key
@ -1086,11 +1113,11 @@ TXDB.prototype.getHistoryHashes = function getHistoryHashes(account, callback) {
}
this.iterate({
gte: account ? 'T/' + account + '/' : 't',
lte: account ? 'T/' + account + '/~' : 't~',
gte: account != null ? 'T/' + account + '/' : 't',
lte: account != null ? 'T/' + account + '/~' : 't~',
transform: function(key) {
key = key.split('/');
if (account)
if (account != null)
return key[4];
return key[3];
}
@ -1110,11 +1137,11 @@ TXDB.prototype.getUnconfirmedHashes = function getUnconfirmedHashes(account, cal
}
this.iterate({
gte: account ? 'P/' + account + '/' : 'p',
lte: account ? 'P/' + account + '/~' : 'p~',
gte: account != null ? 'P/' + account + '/' : 'p',
lte: account != null ? 'P/' + account + '/~' : 'p~',
transform: function(key) {
key = key.split('/');
if (account)
if (account != null)
return key[4];
return key[3];
}
@ -1134,11 +1161,11 @@ TXDB.prototype.getCoinHashes = function getCoinHashes(account, callback) {
}
this.iterate({
gte: account ? 'C/' + account + '/' : 'c',
lte: account ? 'C/' + account + '/~' : 'c~',
gte: account != null ? 'C/' + account + '/' : 'c',
lte: account != null ? 'C/' + account + '/~' : 'c~',
transform: function(key) {
key = key.split('/');
if (account)
if (account != null)
return [key[4], +key[5]];
return [key[3], +key[4]];
}
@ -1157,24 +1184,24 @@ TXDB.prototype.getCoinHashes = function getCoinHashes(account, callback) {
*/
TXDB.prototype.getHeightRangeHashes = function getHeightRangeHashes(account, options, callback) {
if (typeof account !== 'string') {
if (typeof account !== 'number') {
callback = options;
options = account;
account = null;
}
this.iterate({
gte: account
gte: account != null
? 'H/' + account + '/' + pad32(options.start) + '/'
: 'h/' + pad32(options.start) + '/',
lte: account
lte: account != null
? 'H/' + account + '/' + pad32(options.end) + '/~'
: 'h/' + pad32(options.end) + '/~',
limit: options.limit,
reverse: options.reverse,
transform: function(key) {
key = key.split('/');
if (account)
if (account != null)
return key[4];
return key[3];
}
@ -1209,17 +1236,17 @@ TXDB.prototype.getRangeHashes = function getRangeHashes(account, options, callba
}
this.iterate({
gte: account
gte: account != null
? 'M/' + account + '/' + pad32(options.start) + '/'
: 'm/' + pad32(options.start) + '/',
lte: account
lte: account != null
? 'M/' + account + '/' + pad32(options.end) + '/~'
: 'm/' + pad32(options.end) + '/~',
limit: options.limit,
reverse: options.reverse,
transform: function(key) {
key = key.split('/');
if (account)
if (account != null)
return key[4];
return key[3];
}
@ -1237,7 +1264,7 @@ TXDB.prototype.getRangeHashes = function getRangeHashes(account, options, callba
* @param {Function} callback - Returns [Error, {@link TX}[]].
*/
TXDB.prototype.getRange = function getLast(account, options, callback) {
TXDB.prototype.getRange = function getRange(account, options, callback) {
var self = this;
var txs = [];
@ -1308,6 +1335,36 @@ TXDB.prototype.getHistory = function getHistory(account, callback) {
account = null;
}
// Slow case
if (account != null)
return this.getAccountHistory(account, callback);
// Fast case
this.iterate({
gte: 't',
lte: 't~',
values: true,
parse: function(data) {
return bcoin.tx.fromExtended(data);
}
}, callback);
};
/**
* Get all account transactions.
* @param {Number?} account
* @param {Function} callback - Returns [Error, {@link TX}[]].
*/
TXDB.prototype.getAccountHistory = function getAccountHistory(account, callback) {
var self = this;
var txs = [];
if (typeof account === 'function') {
callback = account;
account = null;
}
this.getHistoryHashes(account, function(err, hashes) {
if (err)
return callback(err);
@ -1423,7 +1480,7 @@ TXDB.prototype.getCoins = function getCoins(account, callback) {
}
// Slow case
if (account)
if (account != null)
return this.getAccountCoins(account, callback);
// Fast case
@ -1623,7 +1680,7 @@ TXDB.prototype.toDetails = function toDetails(tx, callback) {
return callback(err);
if (!info)
return callback();
return callback(new Error('Info not found.'));
return callback(null, info.toDetails());
});
@ -1695,7 +1752,7 @@ TXDB.prototype.hasCoin = function hasCoin(hash, index, callback) {
TXDB.prototype.getBalance = function getBalance(account, callback) {
var self = this;
var balance = new Balance(this.wallet.id);
var balance;
if (typeof account === 'function') {
callback = account;
@ -1703,10 +1760,16 @@ TXDB.prototype.getBalance = function getBalance(account, callback) {
}
// Slow case
if (account)
if (account != null)
return this.getAccountBalance(account, callback);
// Really fast case
if (this.balance)
return callback(null, this.balance);
// Fast case
balance = new Balance(this.wallet.id);
this.iterate({
gte: 'c',
lte: 'c~',
@ -1832,7 +1895,7 @@ TXDB.prototype.zap = function zap(account, age, callback, force) {
callback = utils.wrap(callback, unlock);
if (!utils.isNumber(age))
if (!utils.isUInt32(age))
return callback(new Error('Age must be a number.'));
this.getRange(account, {
@ -1863,7 +1926,7 @@ TXDB.prototype.abandon = function abandon(hash, callback, force) {
return callback(err);
if (!result)
return callback(new Error('TX not found.'));
return callback(new Error('TX not eligible.'));
self.remove(hash, callback, force);
});
@ -2016,6 +2079,14 @@ Balance.prototype.toJSON = function toJSON() {
};
};
Balance.prototype.toString = function toString() {
return '<Balance'
+ ' unconfirmed=' + utils.btc(this.unconfirmed)
+ ' confirmed=' + utils.btc(this.confirmed)
+ ' total=' + utils.btc(this.total)
+ '>';
};
/*
* Helpers
*/

View File

@ -170,7 +170,7 @@ Wallet.prototype.init = function init(options, callback) {
self.account = account;
return callback();
self.tx.open(callback);
});
});
};
@ -194,7 +194,7 @@ Wallet.prototype.open = function open(callback) {
self.account = account;
return callback();
self.tx.open(callback);
});
};
@ -207,8 +207,8 @@ Wallet.prototype.destroy = function destroy(callback) {
callback = utils.ensure(callback);
try {
if (this.db.unregister(this))
this.master.destroy();
this.db.unregister(this);
this.master.destroy();
} catch (e) {
this.emit('error', e);
return callback(e);
@ -792,7 +792,7 @@ Wallet.prototype.fund = function fund(tx, options, callback, force) {
fee: options.fee,
subtractFee: options.subtractFee,
changeAddress: account.changeAddress.getAddress(),
height: self.network.height,
height: self.db.height,
rate: rate,
wallet: self,
m: self.m,
@ -850,12 +850,12 @@ Wallet.prototype.createTX = function createTX(options, callback, force) {
// if (options.locktime != null)
// tx.setLocktime(options.locktime);
// else
// tx.avoidFeeSniping(options.height);
// tx.avoidFeeSniping(self.db.height);
if (!tx.isSane())
return callback(new Error('CheckTransaction failed.'));
if (!tx.checkInputs(options.height))
if (!tx.checkInputs(self.db.height))
return callback(new Error('CheckInputs failed.'));
self.scriptInputs(tx, function(err, total) {

View File

@ -52,9 +52,12 @@ function WalletDB(options) {
this.fees = options.fees;
this.logger = options.logger || bcoin.defaultLogger;
this.batches = {};
this.watchers = {};
this.wallets = {};
this.workerPool = null;
this.tip = this.network.genesis.hash;
this.height = 0;
// We need one read lock for `get` and `create`.
// It will hold locks specific to wallet ids.
this.readLock = new ReadLock(this);
@ -140,13 +143,12 @@ WalletDB.prototype._open = function open(callback) {
WalletDB.prototype._close = function close(callback) {
var self = this;
var keys = Object.keys(this.watchers);
var watcher;
var keys = Object.keys(this.wallets);
var wallet;
utils.forEachSerial(keys, function(key, next) {
watcher = self.watchers[key];
watcher.refs = 1;
watcher.object.destroy(next);
wallet = self.wallets[key];
wallet.destroy(next);
}, function(err) {
if (err)
return callback(err);
@ -286,19 +288,9 @@ WalletDB.prototype.dump = function dump(callback) {
* @param {Object} object
*/
WalletDB.prototype.register = function register(object) {
var id = object.id;
if (!this.watchers[id])
this.watchers[id] = { object: object, refs: 0 };
// Should never happen, and if it does, I will cry.
assert(this.watchers[id].object === object, 'I\'m crying.');
// We do some reference counting here
// because we're thug like that (police
// have a fit when your papers legit).
this.watchers[id].refs++;
WalletDB.prototype.register = function register(wallet) {
assert(!this.wallets[wallet.id]);
this.wallets[wallet.id] = wallet;
};
/**
@ -307,25 +299,9 @@ WalletDB.prototype.register = function register(object) {
* @returns {Boolean}
*/
WalletDB.prototype.unregister = function unregister(object) {
var id = object.id;
var watcher = this.watchers[id];
// NOP for now!
return false;
if (!watcher)
return false;
assert(watcher.object === object);
assert(watcher.refs !== 0, '`destroy()` called twice!');
if (--watcher.refs === 0) {
delete this.watchers[id];
return true;
}
return false;
WalletDB.prototype.unregister = function unregister(wallet) {
assert(this.wallets[wallet.id]);
delete this.wallets[wallet.id];
};
/**
@ -336,7 +312,7 @@ WalletDB.prototype.unregister = function unregister(object) {
WalletDB.prototype.get = function get(id, callback) {
var self = this;
var unlock, watcher;
var unlock, wallet;
unlock = this._lock(id, get, [id, callback]);
@ -348,12 +324,10 @@ WalletDB.prototype.get = function get(id, callback) {
if (!id)
return callback();
watcher = this.watchers[id];
wallet = this.wallets[id];
if (watcher) {
watcher.refs++;
return callback(null, watcher.object);
}
if (wallet)
return callback(null, wallet);
this._get(id, function(err, wallet) {
if (err)
@ -521,7 +495,7 @@ WalletDB.prototype.has = function has(id, callback) {
if (!id)
return callback(null, false);
if (this.watchers[id])
if (this.wallets[id])
return callback(null, true);
if (this.walletCache.has(id))
@ -948,7 +922,7 @@ WalletDB.prototype.getWallets = function getWallets(callback) {
WalletDB.prototype.rescan = function rescan(chaindb, callback) {
var self = this;
this.getTip(function(err, hash) {
this.getTip(function(err, hash, height) {
if (err)
return callback(err);
@ -965,7 +939,7 @@ WalletDB.prototype.rescan = function rescan(chaindb, callback) {
self.addTX(tx, function(err) {
if (err)
return next(err);
self.writeTip(block.hash, next);
self.setTip(block.hash, block.height, next);
});
}, callback);
});
@ -989,9 +963,6 @@ WalletDB.prototype.fetchWallet = function fetchWallet(id, callback, handler) {
return callback(new Error('No wallet.'));
handler(wallet, function(err, res1, res2) {
// Kill the reference.
// wallet.destroy();
if (err)
return callback(err);
@ -1102,18 +1073,18 @@ WalletDB.prototype.getTable = function getTable(address, callback) {
WalletDB.prototype.writeGenesis = function writeGenesis(callback) {
var self = this;
var hash;
this.db.has('R', function(err, result) {
this.getTip(function(err, hash, height) {
if (err)
return callback(err);
if (result)
if (hash) {
self.tip = hash;
self.height = height;
return callback();
}
hash = new Buffer(self.network.genesis.hash, 'hex');
self.db.put('R', hash, callback);
self.setTip(self.tip, self.height, callback);
});
};
@ -1124,8 +1095,17 @@ WalletDB.prototype.writeGenesis = function writeGenesis(callback) {
WalletDB.prototype.getTip = function getTip(callback) {
this.db.fetch('R', function(data) {
return data.toString('hex');
}, callback);
var p = new BufferReader(data);
return [p.readHash('hex'), p.readU32()];
}, function(err, items) {
if (err)
return callback(err);
if (!items)
return callback(null, null, -1);
return callback(null, items[0], items[1]);
});
};
/**
@ -1134,10 +1114,22 @@ WalletDB.prototype.getTip = function getTip(callback) {
* @param {Function} callback
*/
WalletDB.prototype.writeTip = function writeTip(hash, callback) {
if (typeof hash === 'string')
hash = new Buffer(hash, 'hex');
this.db.put('R', hash, callback);
WalletDB.prototype.setTip = function setTip(hash, height, callback) {
var self = this;
var p = new BufferWriter();
p.writeHash(hash);
p.writeU32(height);
this.db.put('R', p.render(), function(err) {
if (err)
return callback(err);
self.tip = hash;
self.height = height;
return callback();
});
};
/**
@ -1159,7 +1151,7 @@ WalletDB.prototype.addBlock = function addBlock(block, txs, callback, force) {
if (this.options.useCheckpoints) {
if (block.height < this.network.checkpoints.lastHeight)
return this.writeTip(block.hash, callback);
return this.setTip(block.hash, block.height, callback);
}
if (!Array.isArray(txs))
@ -1171,7 +1163,7 @@ WalletDB.prototype.addBlock = function addBlock(block, txs, callback, force) {
if (err)
return callback(err);
self.writeTip(block.hash, callback);
self.setTip(block.hash, block.height, callback);
});
};
@ -1217,7 +1209,7 @@ WalletDB.prototype.removeBlock = function removeBlock(block, callback, force) {
}, function(err) {
if (err)
return callback(err);
self.writeTip(block.prevBlock, callback);
self.setTip(block.prevBlock, block.height - 1, callback);
});
});
};
@ -1283,300 +1275,6 @@ WalletDB.prototype.getPath = function getPath(id, address, callback) {
});
};
/**
* @see {@link TXDB#toDetails}.
*/
WalletDB.prototype.toDetails = function toDetails(id, tx, callback) {
this.fetchWallet(id, callback, function(wallet, callback) {
wallet.tx.toDetails(tx, callback);
});
};
/**
* @see {@link TXDB#getTX}.
*/
WalletDB.prototype.getTX = function getTX(id, hash, callback) {
this.fetchWallet(id, callback, function(wallet, callback) {
wallet.tx.getTX(hash, callback);
});
};
/**
* @see {@link TXDB#getCoin}.
*/
WalletDB.prototype.getCoin = function getCoin(id, hash, index, callback) {
this.fetchWallet(id, callback, function(wallet, callback) {
wallet.tx.getCoin(hash, index, callback);
});
};
/**
* @see {@link TXDB#getHistory}.
*/
WalletDB.prototype.getHistory = function getHistory(id, account, callback) {
if (typeof account === 'function') {
callback = account;
account = null;
}
this.fetchWallet(id, callback, function(wallet, callback) {
wallet.tx.getHistory(account, callback);
});
};
/**
* @see {@link TXDB#getCoins}.
*/
WalletDB.prototype.getCoins = function getCoins(id, account, callback) {
if (typeof account === 'function') {
callback = account;
account = null;
}
this.fetchWallet(id, callback, function(wallet, callback) {
wallet.tx.getCoins(account, callback);
});
};
/**
* @see {@link TXDB#getUnconfirmed}.
*/
WalletDB.prototype.getUnconfirmed = function getUnconfirmed(id, account, callback) {
if (typeof account === 'function') {
callback = account;
account = null;
}
this.fetchWallet(id, callback, function(wallet, callback) {
wallet.tx.getUnconfirmed(account, callback);
});
};
/**
* @see {@link TXDB#getBalance}.
*/
WalletDB.prototype.getBalance = function getBalance(id, account, callback) {
if (typeof account === 'function') {
callback = account;
account = null;
}
this.fetchWallet(id, callback, function(wallet, callback) {
wallet.tx.getBalance(account, callback);
});
};
/**
* @see {@link TXDB#getLastTime}.
*/
WalletDB.prototype.getLastTime = function getLastTime(id, account, callback) {
if (typeof account === 'function') {
callback = account;
account = null;
}
this.fetchWallet(id, callback, function(wallet, callback) {
wallet.tx.getLastTime(account, callback);
});
};
/**
* @see {@link TXDB#getLast}.
*/
WalletDB.prototype.getLast = function getLast(id, account, limit, callback) {
if (typeof limit === 'function') {
callback = limit;
limit = account;
account = null;
}
this.fetchWallet(id, callback, function(wallet, callback) {
wallet.tx.getLast(account, limit, callback);
});
};
WalletDB.prototype.getTimeRange = function getTimeRange(id, account, options, callback) {
if (typeof options === 'function') {
callback = options;
options = account;
account = null;
}
this.fetchWallet(id, callback, function(wallet, callback) {
wallet.tx.getTimeRange(account, options, callback);
});
};
/**
* @see {@link TXDB#getRange}.
*/
WalletDB.prototype.getRange = function getRange(id, account, options, callback) {
if (typeof options === 'function') {
callback = options;
options = account;
account = null;
}
this.fetchWallet(id, callback, function(wallet, callback) {
wallet.tx.getRange(account, options, callback);
});
};
/**
* @see {@link TXDB#fillHistory}.
*/
WalletDB.prototype.fillHistory = function fillHistory(id, tx, callback) {
this.fetchWallet(id, callback, function(wallet, callback) {
wallet.tx.fillHistory(tx, callback);
});
};
/**
* @see {@link TXDB#fillCoins}.
*/
WalletDB.prototype.fillCoins = function fillCoins(id, tx, callback) {
this.fetchWallet(id, callback, function(wallet, callback) {
wallet.tx.fillCoins(tx, callback);
});
};
/**
* Zap all walletdb transactions.
* @see {@link TXDB#zap}.
*/
WalletDB.prototype.zap = function zap(id, account, age, callback) {
if (typeof age === 'function') {
callback = age;
age = account;
account = null;
}
this.fetchWallet(id, callback, function(wallet, callback) {
wallet.tx.zap(account, age, callback);
});
};
WalletDB.prototype.createAddress = function createAddress(id, name, change, callback) {
this.fetchWallet(id, callback, function(wallet, callback) {
wallet.createAddress(name, change, callback);
});
};
WalletDB.prototype.fund = function fund(id, tx, options, callback) {
this.fetchWallet(id, callback, function(wallet, callback) {
wallet.fund(tx, options, callback);
});
};
WalletDB.prototype.scriptInputs = function scriptInputs(id, tx, callback) {
this.fetchWallet(id, callback, function(wallet, callback) {
wallet.scriptInputs(tx, callback);
});
};
WalletDB.prototype.sign = function sign(id, tx, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
this.fetchWallet(id, callback, function(wallet, callback) {
wallet.sign(tx, options, callback);
});
};
WalletDB.prototype.createTX = function createTX(id, options, callback) {
this.fetchWallet(id, callback, function(wallet, callback) {
wallet.createTX(options, callback);
});
};
WalletDB.prototype.send = function send(id, options, callback) {
this.fetchWallet(id, callback, function(wallet, callback) {
wallet.send(options, callback);
});
};
WalletDB.prototype.addKey = function addKey(id, name, key, callback) {
this.fetchWallet(id, callback, function(wallet, callback) {
wallet.addKey(name, key, callback);
});
};
WalletDB.prototype.removeKey = function removeKey(id, name, key, callback) {
this.fetchWallet(id, callback, function(wallet, callback) {
wallet.removeKey(name, key, callback);
});
};
WalletDB.prototype.setPassphrase = function setPassphrase(id, old, new_, callback) {
if (typeof new_ === 'function') {
callback = new_;
new_ = old;
old = null;
}
this.fetchWallet(id, callback, function(wallet, callback) {
wallet.setPassphrase(old, new_, callback);
});
};
WalletDB.prototype.retoken = function retoken(id, passphrase, callback) {
if (typeof passphrase === 'function') {
callback = passphrase;
passphrase = null;
}
this.fetchWallet(id, callback, function(wallet, callback) {
wallet.retoken(passphrase, callback);
});
};
WalletDB.prototype.getInfo = function getInfo(id, callback) {
this.fetchWallet(id, callback, function(wallet, callback) {
callback(null, wallet);
});
};
WalletDB.prototype.ensureAccount = function ensureAccount(id, options, callback) {
var self = this;
var account = options.account;
if (typeof options.name === 'string')
account = options.name;
this.fetchWallet(id, callback, function(wallet, callback) {
wallet.hasAccount(account, function(err, exists) {
if (err)
return callback(err);
if (exists)
return wallet.getAccount(account, callback);
wallet.createAccount(options, callback);
});
});
};
WalletDB.prototype.getRedeem = function getRedeem(id, hash, callback) {
this.fetchWallet(id, callback, function(wallet, callback) {
wallet.getRedeem(hash, callback);
});
};
/**
* Path
* @constructor