wallet: client refactor.
This commit is contained in:
parent
aedbe27b6e
commit
771a4ef17f
@ -187,8 +187,8 @@ AsyncEmitter.prototype.listeners = function listeners(type) {
|
||||
|
||||
const result = [];
|
||||
|
||||
for (const listener of listeners)
|
||||
result.push(listener.handler);
|
||||
for (const {handler} of listeners)
|
||||
result.push(handler);
|
||||
|
||||
return result;
|
||||
};
|
||||
@ -215,10 +215,21 @@ AsyncEmitter.prototype.listenerCount = function listenerCount(type) {
|
||||
* @param {Function} handler
|
||||
*/
|
||||
|
||||
AsyncEmitter.prototype.listen = function listen(type, handler) {
|
||||
AsyncEmitter.prototype.bind = function bind(type, handler) {
|
||||
return this.on(type, handler);
|
||||
};
|
||||
|
||||
/**
|
||||
* Emit event.
|
||||
* @param {String} type
|
||||
* @param {...Object} args
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
AsyncEmitter.prototype.fire = function fire() {
|
||||
return this.emit.apply(this, arguments);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a listener.
|
||||
* @param {String} type
|
||||
@ -236,24 +247,42 @@ AsyncEmitter.prototype.hook = function hook(type, handler) {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
AsyncEmitter.prototype.fire = function fire() {
|
||||
return this.emit.apply(this, arguments);
|
||||
AsyncEmitter.prototype.call = function call() {
|
||||
return this.emitAsync.apply(this, arguments);
|
||||
};
|
||||
|
||||
/**
|
||||
* Emit an event synchronously.
|
||||
* @method
|
||||
* @param {String} type
|
||||
* @param {...Object} args
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
AsyncEmitter.prototype.emit = function emit(type) {
|
||||
try {
|
||||
this._emit.apply(this, arguments);
|
||||
} catch (e) {
|
||||
if (type === 'error')
|
||||
throw e;
|
||||
|
||||
this._emit('error', e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Emit an event synchronously.
|
||||
* @private
|
||||
* @param {String} type
|
||||
* @param {...Object} args
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
AsyncEmitter.prototype._emit = function _emit(type) {
|
||||
assert(typeof type === 'string', '`type` must be a string.');
|
||||
|
||||
const listeners = this._events[type];
|
||||
|
||||
if (!listeners || listeners.length === 0) {
|
||||
if (!listeners) {
|
||||
if (type === 'error') {
|
||||
const error = arguments[1];
|
||||
|
||||
@ -267,7 +296,9 @@ AsyncEmitter.prototype.emit = function emit(type) {
|
||||
return;
|
||||
}
|
||||
|
||||
let args;
|
||||
assert(listeners.length > 0);
|
||||
|
||||
let args = null;
|
||||
|
||||
for (let i = 0; i < listeners.length; i++) {
|
||||
const listener = listeners[i];
|
||||
@ -311,12 +342,31 @@ AsyncEmitter.prototype.emit = function emit(type) {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
AsyncEmitter.prototype.call = async function call(type) {
|
||||
AsyncEmitter.prototype.emitAsync = async function emitAsync(type) {
|
||||
try {
|
||||
await this._emitAsync.apply(this, arguments);
|
||||
} catch (e) {
|
||||
if (type === 'error')
|
||||
throw e;
|
||||
|
||||
await this._emitAsync('error', e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Emit an event. Wait for promises to resolve.
|
||||
* @private
|
||||
* @param {String} type
|
||||
* @param {...Object} args
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
AsyncEmitter.prototype._emitAsync = async function _emitAsync(type) {
|
||||
assert(typeof type === 'string', '`type` must be a string.');
|
||||
|
||||
const listeners = this._events[type];
|
||||
|
||||
if (!listeners || listeners.length === 0) {
|
||||
if (!listeners) {
|
||||
if (type === 'error') {
|
||||
const error = arguments[1];
|
||||
|
||||
@ -330,7 +380,9 @@ AsyncEmitter.prototype.call = async function call(type) {
|
||||
return;
|
||||
}
|
||||
|
||||
let args;
|
||||
assert(listeners.length > 0);
|
||||
|
||||
let args = null;
|
||||
|
||||
for (let i = 0; i < listeners.length; i++) {
|
||||
const listener = listeners[i];
|
||||
@ -366,29 +418,6 @@ AsyncEmitter.prototype.call = async function call(type) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Emit an event. Ignore rejections.
|
||||
* @method
|
||||
* @param {String} type
|
||||
* @param {...Object} args
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
AsyncEmitter.prototype.tryCall = async function tryCall(type) {
|
||||
try {
|
||||
await this.call.apply(this, arguments);
|
||||
} catch (e) {
|
||||
if (type === 'error')
|
||||
return;
|
||||
|
||||
try {
|
||||
await this.call('error', e);
|
||||
} catch (e) {
|
||||
;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Event Listener
|
||||
* @constructor
|
||||
|
||||
@ -157,7 +157,7 @@ AsyncObject.prototype._close = function _close(callback) {
|
||||
* @param {Function} handler
|
||||
*/
|
||||
|
||||
AsyncObject.prototype.listen = function listen(type, handler) {
|
||||
AsyncObject.prototype.bind = function bind(type, handler) {
|
||||
return this.on(type, handler);
|
||||
};
|
||||
|
||||
|
||||
@ -26,15 +26,15 @@ class WalletClient extends NodeClient {
|
||||
super(options);
|
||||
}
|
||||
|
||||
listen(event, handler) {
|
||||
bind(event, handler) {
|
||||
const parser = parsers[event];
|
||||
|
||||
if (!parser) {
|
||||
super.listen(event, handler);
|
||||
super.bind(event, handler);
|
||||
return;
|
||||
}
|
||||
|
||||
super.listen(event, (...args) => {
|
||||
super.bind(event, (...args) => {
|
||||
return handler(...parser(...args));
|
||||
});
|
||||
}
|
||||
|
||||
@ -71,10 +71,9 @@ NodeClient.prototype._init = function _init() {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
NodeClient.prototype._open = function _open(options) {
|
||||
NodeClient.prototype._open = async function _open(options) {
|
||||
this.listening = true;
|
||||
this.emit('open');
|
||||
return Promise.resolve();
|
||||
setImmediate(() => this.emit('connect'));
|
||||
};
|
||||
|
||||
/**
|
||||
@ -82,10 +81,9 @@ NodeClient.prototype._open = function _open(options) {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
NodeClient.prototype._close = function _close() {
|
||||
NodeClient.prototype._close = async function _close() {
|
||||
this.listening = false;
|
||||
this.emit('close');
|
||||
return Promise.resolve();
|
||||
setImmediate(() => this.emit('disconnect'));
|
||||
};
|
||||
|
||||
/**
|
||||
@ -93,8 +91,8 @@ NodeClient.prototype._close = function _close() {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
NodeClient.prototype.getTip = function getTip() {
|
||||
return Promise.resolve(this.node.chain.tip);
|
||||
NodeClient.prototype.getTip = async function getTip() {
|
||||
return this.node.chain.tip;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -121,9 +119,8 @@ NodeClient.prototype.getEntry = async function getEntry(hash) {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
NodeClient.prototype.send = function send(tx) {
|
||||
NodeClient.prototype.send = async function send(tx) {
|
||||
this.node.relay(tx);
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
/**
|
||||
@ -132,10 +129,9 @@ NodeClient.prototype.send = function send(tx) {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
NodeClient.prototype.setFilter = function setFilter(filter) {
|
||||
NodeClient.prototype.setFilter = async function setFilter(filter) {
|
||||
this.filter = filter;
|
||||
this.node.pool.setFilter(filter);
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
/**
|
||||
@ -144,9 +140,8 @@ NodeClient.prototype.setFilter = function setFilter(filter) {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
NodeClient.prototype.addFilter = function addFilter(data) {
|
||||
NodeClient.prototype.addFilter = async function addFilter(data) {
|
||||
this.node.pool.queueFilterLoad();
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
/**
|
||||
@ -154,9 +149,8 @@ NodeClient.prototype.addFilter = function addFilter(data) {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
NodeClient.prototype.resetFilter = function resetFilter() {
|
||||
NodeClient.prototype.resetFilter = async function resetFilter() {
|
||||
this.node.pool.queueFilterLoad();
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
/**
|
||||
@ -191,7 +185,7 @@ NodeClient.prototype.getHashes = async function getHashes(start = -1, end = -1)
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
NodeClient.prototype.rescan = function rescan(start) {
|
||||
NodeClient.prototype.rescan = async function rescan(start) {
|
||||
return this.node.chain.scan(start, this.filter, (entry, txs) => {
|
||||
return this.call('block rescan', entry, txs);
|
||||
});
|
||||
|
||||
@ -194,7 +194,7 @@ Wallet.prototype.init = async function init(options) {
|
||||
|
||||
this.logger.info('Wallet initialized (%s).', this.id);
|
||||
|
||||
await this.txdb.open(this);
|
||||
return this.txdb.open(this);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -212,7 +212,7 @@ Wallet.prototype.open = async function open() {
|
||||
|
||||
this.logger.info('Wallet opened (%s).', this.id);
|
||||
|
||||
await this.txdb.open(this);
|
||||
return this.txdb.open(this);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -665,7 +665,7 @@ Wallet.prototype.ensureAccount = async function ensureAccount(options, passphras
|
||||
if (account)
|
||||
return account;
|
||||
|
||||
return await this.createAccount(options, passphrase);
|
||||
return this.createAccount(options, passphrase);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -701,7 +701,7 @@ Wallet.prototype.getAccountHashes = async function getAccountHashes(acct) {
|
||||
if (index === -1)
|
||||
throw new Error('Account not found.');
|
||||
|
||||
return await this.wdb.getAccountHashes(this.wid, index);
|
||||
return this.wdb.getAccountHashes(this.wid, index);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -774,7 +774,7 @@ Wallet.prototype.getAccountName = async function getAccountName(index) {
|
||||
if (typeof index === 'string')
|
||||
return index;
|
||||
|
||||
return await this.wdb.getAccountName(this.wid, index);
|
||||
return this.wdb.getAccountName(this.wid, index);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -789,7 +789,7 @@ Wallet.prototype.hasAccount = async function hasAccount(acct) {
|
||||
if (index === -1)
|
||||
return false;
|
||||
|
||||
return await this.db.hasAccount(this.wid, index);
|
||||
return this.db.hasAccount(this.wid, index);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -912,7 +912,7 @@ Wallet.prototype.readPath = async function readPath(address) {
|
||||
|
||||
Wallet.prototype.hasPath = async function hasPath(address) {
|
||||
const hash = Address.getHash(address, 'hex');
|
||||
return await this.wdb.hasPath(this.wid, hash);
|
||||
return this.wdb.hasPath(this.wid, hash);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -923,20 +923,9 @@ Wallet.prototype.hasPath = async function hasPath(address) {
|
||||
|
||||
Wallet.prototype.getPaths = async function getPaths(acct) {
|
||||
if (acct != null)
|
||||
return await this.getAccountPaths(acct);
|
||||
return this.getAccountPaths(acct);
|
||||
|
||||
const paths = await this.wdb.getWalletPaths(this.wid);
|
||||
const result = [];
|
||||
|
||||
for (const path of paths) {
|
||||
path.name = await this.getAccountName(path.account);
|
||||
|
||||
assert(path.name);
|
||||
|
||||
result.push(path);
|
||||
}
|
||||
|
||||
return result;
|
||||
return this.wdb.getWalletPaths(this.wid);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -1184,7 +1173,7 @@ Wallet.prototype.getAccountByAddress = async function getAccountByAddress(addres
|
||||
if (!path)
|
||||
return null;
|
||||
|
||||
return await this.getAccount(path.account);
|
||||
return this.getAccount(path.account);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -1937,7 +1926,7 @@ Wallet.prototype.zap = async function zap(acct, age) {
|
||||
|
||||
Wallet.prototype._zap = async function _zap(acct, age) {
|
||||
const account = await this.ensureIndex(acct);
|
||||
return await this.txdb.zap(account, age);
|
||||
return this.txdb.zap(account, age);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -2021,7 +2010,7 @@ Wallet.prototype.getHistory = async function getHistory(acct) {
|
||||
|
||||
Wallet.prototype.getCoins = async function getCoins(acct) {
|
||||
const account = await this.ensureIndex(acct);
|
||||
return await this.txdb.getCoins(account);
|
||||
return this.txdb.getCoins(account);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -2032,7 +2021,7 @@ Wallet.prototype.getCoins = async function getCoins(acct) {
|
||||
|
||||
Wallet.prototype.getCredits = async function getCredits(acct) {
|
||||
const account = await this.ensureIndex(acct);
|
||||
return await this.txdb.getCredits(account);
|
||||
return this.txdb.getCredits(account);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -2082,7 +2071,7 @@ Wallet.prototype.getSmartCoins = async function getSmartCoins(acct) {
|
||||
|
||||
Wallet.prototype.getPending = async function getPending(acct) {
|
||||
const account = await this.ensureIndex(acct);
|
||||
return await this.txdb.getPending(account);
|
||||
return this.txdb.getPending(account);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -2093,7 +2082,7 @@ Wallet.prototype.getPending = async function getPending(acct) {
|
||||
|
||||
Wallet.prototype.getBalance = async function getBalance(acct) {
|
||||
const account = await this.ensureIndex(acct);
|
||||
return await this.txdb.getBalance(account);
|
||||
return this.txdb.getBalance(account);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -2107,7 +2096,7 @@ Wallet.prototype.getBalance = async function getBalance(acct) {
|
||||
|
||||
Wallet.prototype.getRange = async function getRange(acct, options) {
|
||||
const account = await this.ensureIndex(acct);
|
||||
return await this.txdb.getRange(account, options);
|
||||
return this.txdb.getRange(account, options);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -2119,7 +2108,7 @@ Wallet.prototype.getRange = async function getRange(acct, options) {
|
||||
|
||||
Wallet.prototype.getLast = async function getLast(acct, limit) {
|
||||
const account = await this.ensureIndex(acct);
|
||||
return await this.txdb.getLast(account, limit);
|
||||
return this.txdb.getLast(account, limit);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -28,6 +28,7 @@ const Outpoint = require('../primitives/outpoint');
|
||||
const layouts = require('./layout');
|
||||
const records = require('./records');
|
||||
const StaticWriter = require('../utils/staticwriter');
|
||||
const NullClient = require('./nullclient');
|
||||
const layout = layouts.walletdb;
|
||||
const ChainState = records.ChainState;
|
||||
const BlockMeta = records.BlockMeta;
|
||||
@ -53,7 +54,7 @@ function WalletDB(options) {
|
||||
this.network = this.options.network;
|
||||
this.logger = this.options.logger.context('wallet');
|
||||
this.workers = this.options.workers;
|
||||
this.client = this.options.client;
|
||||
this.client = this.options.client || new NullClient(this);
|
||||
this.feeRate = this.options.feeRate;
|
||||
this.db = LDB(this.options);
|
||||
|
||||
@ -63,13 +64,17 @@ function WalletDB(options) {
|
||||
this.wallets = new Map();
|
||||
this.depth = 0;
|
||||
this.rescanning = false;
|
||||
this.bound = false;
|
||||
|
||||
// Wallet read lock.
|
||||
this.readLock = new MappedLock();
|
||||
this.writeLock = new Lock();
|
||||
this.txLock = new Lock();
|
||||
this.scanLock = new Lock();
|
||||
|
||||
// Wallet write lock (creation and rename).
|
||||
this.writeLock = new Lock();
|
||||
|
||||
// Lock for handling anything tx related.
|
||||
this.txLock = new Lock();
|
||||
|
||||
// Address and outpoint filter.
|
||||
this.filter = new Bloom();
|
||||
|
||||
this._init();
|
||||
@ -104,6 +109,66 @@ WalletDB.prototype._init = function _init() {
|
||||
}
|
||||
|
||||
this.filter = Bloom.fromRate(items, 0.001, flag);
|
||||
this.bind();
|
||||
};
|
||||
|
||||
/**
|
||||
* Bind to node events.
|
||||
* @private
|
||||
*/
|
||||
|
||||
WalletDB.prototype.bind = function bind() {
|
||||
this.client.on('error', (err) => {
|
||||
this.emit('error', err);
|
||||
});
|
||||
|
||||
this.client.on('connect', async () => {
|
||||
try {
|
||||
await this.syncNode();
|
||||
} catch (e) {
|
||||
this.emit('error', e);
|
||||
}
|
||||
});
|
||||
|
||||
this.client.bind('block connect', async (entry, txs) => {
|
||||
try {
|
||||
await this.addBlock(entry, txs);
|
||||
} catch (e) {
|
||||
this.emit('error', e);
|
||||
}
|
||||
});
|
||||
|
||||
this.client.bind('block disconnect', async (entry) => {
|
||||
try {
|
||||
await this.removeBlock(entry);
|
||||
} catch (e) {
|
||||
this.emit('error', e);
|
||||
}
|
||||
});
|
||||
|
||||
this.client.hook('block rescan', async (entry, txs) => {
|
||||
try {
|
||||
await this.rescanBlock(entry, txs);
|
||||
} catch (e) {
|
||||
this.emit('error', e);
|
||||
}
|
||||
});
|
||||
|
||||
this.client.bind('tx', async (tx) => {
|
||||
try {
|
||||
await this.addTX(tx);
|
||||
} catch (e) {
|
||||
this.emit('error', e);
|
||||
}
|
||||
});
|
||||
|
||||
this.client.bind('chain reset', async (tip) => {
|
||||
try {
|
||||
await this.resetChain(tip);
|
||||
} catch (e) {
|
||||
this.emit('error', e);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@ -121,7 +186,8 @@ WalletDB.prototype._open = async function _open() {
|
||||
if (this.options.wipeNoReally)
|
||||
await this.wipe();
|
||||
|
||||
await this.load();
|
||||
await this.watch();
|
||||
await this.connect();
|
||||
|
||||
this.logger.info(
|
||||
'WalletDB loaded (depth=%d, height=%d, start=%d).',
|
||||
@ -159,182 +225,6 @@ WalletDB.prototype._close = async function _close() {
|
||||
await this.db.close();
|
||||
};
|
||||
|
||||
/**
|
||||
* Load the walletdb.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
WalletDB.prototype.load = async function load() {
|
||||
const unlock = await this.txLock.lock();
|
||||
try {
|
||||
await this.watch();
|
||||
await this.connect();
|
||||
await this.init();
|
||||
} finally {
|
||||
unlock();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sync state with server on every connect.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
WalletDB.prototype.syncNode = async function syncNode() {
|
||||
const unlock = await this.txLock.lock();
|
||||
try {
|
||||
this.logger.info('Resyncing from server...');
|
||||
await this.setFilter();
|
||||
await this.syncChain();
|
||||
await this.resend();
|
||||
} finally {
|
||||
unlock();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Bind to node events.
|
||||
* @private
|
||||
*/
|
||||
|
||||
WalletDB.prototype.bind = function bind() {
|
||||
if (!this.client)
|
||||
return;
|
||||
|
||||
if (this.bound)
|
||||
return;
|
||||
|
||||
this.bound = true;
|
||||
|
||||
this.client.on('error', (err) => {
|
||||
this.emit('error', err);
|
||||
});
|
||||
|
||||
this.client.on('open', async () => {
|
||||
try {
|
||||
await this.syncNode();
|
||||
} catch (e) {
|
||||
this.emit('error', e);
|
||||
}
|
||||
});
|
||||
|
||||
this.client.listen('block connect', async (entry, txs) => {
|
||||
try {
|
||||
await this.addBlock(entry, txs);
|
||||
} catch (e) {
|
||||
this.emit('error', e);
|
||||
}
|
||||
});
|
||||
|
||||
this.client.listen('block disconnect', async (entry) => {
|
||||
try {
|
||||
await this.removeBlock(entry);
|
||||
} catch (e) {
|
||||
this.emit('error', e);
|
||||
}
|
||||
});
|
||||
|
||||
this.client.hook('block rescan', async (entry, txs) => {
|
||||
try {
|
||||
await this.rescanBlock(entry, txs);
|
||||
} catch (e) {
|
||||
this.emit('error', e);
|
||||
}
|
||||
});
|
||||
|
||||
this.client.listen('tx', async (tx) => {
|
||||
try {
|
||||
await this.addTX(tx);
|
||||
} catch (e) {
|
||||
this.emit('error', e);
|
||||
}
|
||||
});
|
||||
|
||||
this.client.listen('chain reset', async (tip) => {
|
||||
try {
|
||||
await this.resetChain(tip);
|
||||
} catch (e) {
|
||||
this.emit('error', e);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Connect to the node server (client required).
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
WalletDB.prototype.connect = async function connect() {
|
||||
if (!this.client)
|
||||
return;
|
||||
|
||||
this.bind();
|
||||
|
||||
await this.client.open();
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnect from node server (client required).
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
WalletDB.prototype.disconnect = async function disconnect() {
|
||||
if (!this.client)
|
||||
return;
|
||||
|
||||
await this.client.close();
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize and write initial sync state.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
WalletDB.prototype.init = async function init() {
|
||||
const cache = await this.getState();
|
||||
|
||||
if (cache) {
|
||||
this.state = cache;
|
||||
this.height = cache.height;
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.info('Initializing database state from server.');
|
||||
|
||||
const b = this.db.batch();
|
||||
|
||||
let tip = null;
|
||||
|
||||
if (this.client) {
|
||||
const hashes = await this.client.getHashes();
|
||||
|
||||
for (let height = 0; height < hashes.length; height++) {
|
||||
const hash = hashes[height];
|
||||
const meta = new BlockMeta(hash, height);
|
||||
b.put(layout.h(height), meta.toHash());
|
||||
tip = meta;
|
||||
}
|
||||
} else {
|
||||
tip = new BlockMeta(this.network.genesis.hash, 0);
|
||||
b.put(layout.h(0), tip.toHash());
|
||||
}
|
||||
|
||||
assert(tip);
|
||||
|
||||
const state = this.state.clone();
|
||||
state.startHeight = tip.height;
|
||||
state.startHash = tip.hash;
|
||||
state.height = tip.height;
|
||||
state.marked = false;
|
||||
|
||||
b.put(layout.R, state.toRaw());
|
||||
|
||||
await b.write();
|
||||
|
||||
this.state = state;
|
||||
this.height = state.height;
|
||||
};
|
||||
|
||||
/**
|
||||
* Watch addresses and outpoints.
|
||||
* @private
|
||||
@ -379,6 +269,87 @@ WalletDB.prototype.watch = async function watch() {
|
||||
this.logger.info('Added %d outpoints to WalletDB filter.', outpoints);
|
||||
};
|
||||
|
||||
/**
|
||||
* Connect to the node server (client required).
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
WalletDB.prototype.connect = async function connect() {
|
||||
return this.client.open();
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnect from node server (client required).
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
WalletDB.prototype.disconnect = async function disconnect() {
|
||||
return this.client.close();
|
||||
};
|
||||
|
||||
/**
|
||||
* Sync state with server on every connect.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
WalletDB.prototype.syncNode = async function syncNode() {
|
||||
const unlock = await this.txLock.lock();
|
||||
try {
|
||||
this.logger.info('Resyncing from server...');
|
||||
await this.syncState();
|
||||
await this.syncFilter();
|
||||
await this.syncChain();
|
||||
await this.resend();
|
||||
} finally {
|
||||
unlock();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize and write initial sync state.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
WalletDB.prototype.syncState = async function syncState() {
|
||||
const cache = await this.getState();
|
||||
|
||||
if (cache) {
|
||||
this.state = cache;
|
||||
this.height = cache.height;
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.info('Initializing database state from server.');
|
||||
|
||||
const b = this.db.batch();
|
||||
|
||||
let tip = null;
|
||||
|
||||
const hashes = await this.client.getHashes();
|
||||
|
||||
for (let height = 0; height < hashes.length; height++) {
|
||||
const hash = hashes[height];
|
||||
const meta = new BlockMeta(hash, height);
|
||||
b.put(layout.h(height), meta.toHash());
|
||||
tip = meta;
|
||||
}
|
||||
|
||||
assert(tip);
|
||||
|
||||
const state = this.state.clone();
|
||||
state.startHeight = tip.height;
|
||||
state.startHash = tip.hash;
|
||||
state.height = tip.height;
|
||||
state.marked = false;
|
||||
|
||||
b.put(layout.R, state.toRaw());
|
||||
|
||||
await b.write();
|
||||
|
||||
this.state = state;
|
||||
this.height = state.height;
|
||||
};
|
||||
|
||||
/**
|
||||
* Connect and sync with the chain server.
|
||||
* @private
|
||||
@ -386,11 +357,7 @@ WalletDB.prototype.watch = async function watch() {
|
||||
*/
|
||||
|
||||
WalletDB.prototype.syncChain = async function syncChain() {
|
||||
if (!this.client)
|
||||
return;
|
||||
|
||||
let height = this.state.height;
|
||||
let entry = null;
|
||||
|
||||
this.logger.info('Syncing state from height %d.', height);
|
||||
|
||||
@ -398,17 +365,13 @@ WalletDB.prototype.syncChain = async function syncChain() {
|
||||
const tip = await this.getBlock(height);
|
||||
assert(tip);
|
||||
|
||||
entry = await this.client.getEntry(tip.hash);
|
||||
|
||||
if (entry)
|
||||
if (await this.client.getEntry(tip.hash))
|
||||
break;
|
||||
|
||||
assert(height !== 0);
|
||||
height -= 1;
|
||||
}
|
||||
|
||||
assert(entry);
|
||||
|
||||
await this.scan(height);
|
||||
};
|
||||
|
||||
@ -420,9 +383,6 @@ WalletDB.prototype.syncChain = async function syncChain() {
|
||||
*/
|
||||
|
||||
WalletDB.prototype.scan = async function scan(height) {
|
||||
if (!this.client)
|
||||
return;
|
||||
|
||||
if (height == null)
|
||||
height = this.state.startHeight;
|
||||
|
||||
@ -467,7 +427,7 @@ WalletDB.prototype.rescan = async function rescan(height) {
|
||||
*/
|
||||
|
||||
WalletDB.prototype._rescan = async function _rescan(height) {
|
||||
return await this.scan(height);
|
||||
return this.scan(height);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -477,12 +437,7 @@ WalletDB.prototype._rescan = async function _rescan(height) {
|
||||
*/
|
||||
|
||||
WalletDB.prototype.send = async function send(tx) {
|
||||
if (!this.client) {
|
||||
this.emit('send', tx);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.client.send(tx);
|
||||
return this.client.send(tx);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -495,9 +450,6 @@ WalletDB.prototype.estimateFee = async function estimateFee(blocks) {
|
||||
if (this.feeRate > 0)
|
||||
return this.feeRate;
|
||||
|
||||
if (!this.client)
|
||||
return this.network.feeRate;
|
||||
|
||||
const rate = await this.client.estimateFee(blocks);
|
||||
|
||||
if (rate < this.network.feeRate)
|
||||
@ -515,12 +467,7 @@ WalletDB.prototype.estimateFee = async function estimateFee(blocks) {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
WalletDB.prototype.setFilter = function setFilter() {
|
||||
if (!this.client) {
|
||||
this.emit('set filter', this.filter);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
WalletDB.prototype.syncFilter = function syncFilter() {
|
||||
this.logger.info('Sending filter to server (%dmb).',
|
||||
this.filter.size / 8 / (1 << 20));
|
||||
|
||||
@ -535,11 +482,6 @@ WalletDB.prototype.setFilter = function setFilter() {
|
||||
*/
|
||||
|
||||
WalletDB.prototype.addFilter = function addFilter(data) {
|
||||
if (!this.client) {
|
||||
this.emit('add filter', data);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return this.client.addFilter(data);
|
||||
};
|
||||
|
||||
@ -550,11 +492,6 @@ WalletDB.prototype.addFilter = function addFilter(data) {
|
||||
*/
|
||||
|
||||
WalletDB.prototype.resetFilter = function resetFilter() {
|
||||
if (!this.client) {
|
||||
this.emit('reset filter');
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return this.client.resetFilter();
|
||||
};
|
||||
|
||||
@ -894,9 +831,7 @@ WalletDB.prototype.create = async function create(options) {
|
||||
*/
|
||||
|
||||
WalletDB.prototype._create = async function _create(options) {
|
||||
const exists = await this.has(options.id);
|
||||
|
||||
if (exists)
|
||||
if (await this.has(options.id))
|
||||
throw new Error('WDB: Wallet already exists.');
|
||||
|
||||
const wallet = Wallet.fromOptions(this, options);
|
||||
@ -937,7 +872,7 @@ WalletDB.prototype.ensure = async function ensure(options) {
|
||||
if (wallet)
|
||||
return wallet;
|
||||
|
||||
return await this.create(options);
|
||||
return this.create(options);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -1196,6 +1131,8 @@ WalletDB.prototype.getWalletPaths = async function getWalletPaths(wid) {
|
||||
const path = Path.fromRaw(item.value);
|
||||
|
||||
path.hash = hash;
|
||||
path.name = await this.getAccountName(wid, path.account);
|
||||
assert(path.name);
|
||||
|
||||
paths.push(path);
|
||||
}
|
||||
@ -1342,9 +1279,7 @@ WalletDB.prototype.resendPending = async function resendPending(wid) {
|
||||
txs.push(wtx.tx);
|
||||
}
|
||||
|
||||
const sorted = common.sortDeps(txs);
|
||||
|
||||
for (const tx of sorted)
|
||||
for (const tx of common.sortDeps(txs))
|
||||
await this.send(tx);
|
||||
};
|
||||
|
||||
@ -1415,7 +1350,7 @@ WalletDB.prototype.getState = async function getState() {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
WalletDB.prototype.syncTip = async function syncTip(tip) {
|
||||
WalletDB.prototype.setTip = async function setTip(tip) {
|
||||
const b = this.db.batch();
|
||||
const state = this.state.clone();
|
||||
|
||||
@ -1722,7 +1657,7 @@ WalletDB.prototype.rollback = async function rollback(height) {
|
||||
assert(tip);
|
||||
|
||||
await this.revert(tip.height);
|
||||
await this.syncTip(tip);
|
||||
await this.setTip(tip);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -1804,9 +1739,9 @@ WalletDB.prototype._addBlock = async function _addBlock(entry, txs) {
|
||||
}
|
||||
|
||||
// Sync the state to the new tip.
|
||||
await this.syncTip(tip);
|
||||
await this.setTip(tip);
|
||||
|
||||
if (this.options.checkpoints) {
|
||||
if (this.options.checkpoints && !this.state.marked) {
|
||||
if (tip.height <= this.network.lastCheckpoint)
|
||||
return 0;
|
||||
}
|
||||
@ -1814,7 +1749,7 @@ WalletDB.prototype._addBlock = async function _addBlock(entry, txs) {
|
||||
let total = 0;
|
||||
|
||||
for (const tx of txs) {
|
||||
if (await this._insert(tx, tip))
|
||||
if (await this._addTX(tx, tip))
|
||||
total += 1;
|
||||
}
|
||||
|
||||
@ -1872,7 +1807,7 @@ WalletDB.prototype._removeBlock = async function _removeBlock(entry) {
|
||||
const map = await this.getBlockMap(tip.height);
|
||||
|
||||
if (!map) {
|
||||
await this.syncTip(prev);
|
||||
await this.setTip(prev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1885,7 +1820,7 @@ WalletDB.prototype._removeBlock = async function _removeBlock(entry) {
|
||||
}
|
||||
|
||||
// Sync the state to the previous tip.
|
||||
await this.syncTip(prev);
|
||||
await this.setTip(prev);
|
||||
|
||||
this.logger.warning('Disconnected wallet block %s (tx=%d).',
|
||||
util.revHex(tip.hash), total);
|
||||
@ -1931,7 +1866,7 @@ WalletDB.prototype.rescanBlock = async function rescanBlock(entry, txs) {
|
||||
WalletDB.prototype.addTX = async function addTX(tx) {
|
||||
const unlock = await this.txLock.lock();
|
||||
try {
|
||||
return await this._insert(tx);
|
||||
return await this._addTX(tx);
|
||||
} finally {
|
||||
unlock();
|
||||
}
|
||||
@ -1945,7 +1880,7 @@ WalletDB.prototype.addTX = async function addTX(tx) {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
WalletDB.prototype._insert = async function _insert(tx, block) {
|
||||
WalletDB.prototype._addTX = async function _addTX(tx, block) {
|
||||
const wids = await this.getWalletsByTX(tx);
|
||||
|
||||
assert(!tx.mutable, 'WDB: Cannot add mutable TX.');
|
||||
@ -2055,10 +1990,8 @@ function WalletOptions(options) {
|
||||
*/
|
||||
|
||||
WalletOptions.prototype.fromOptions = function fromOptions(options) {
|
||||
if (options.network != null) {
|
||||
if (options.network != null)
|
||||
this.network = Network.get(options.network);
|
||||
this.port = this.network.rpcPort + 2;
|
||||
}
|
||||
|
||||
if (options.logger != null) {
|
||||
assert(typeof options.logger === 'object');
|
||||
|
||||
Loading…
Reference in New Issue
Block a user