walletdb: start separating out walletdb.

This commit is contained in:
Christopher Jeffrey 2016-10-24 15:13:45 -07:00
parent ffc17b48c2
commit 3b333c07cd
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
9 changed files with 374 additions and 282 deletions

View File

@ -479,6 +479,7 @@ ChainDB.prototype.getHash = co(function* getHash(height) {
ChainDB.prototype.getChainHeight = co(function* getChainHeight() {
var entry = yield this.getTip();
if (!entry)
return -1;
@ -676,7 +677,12 @@ ChainDB.prototype.getEntries = function getEntries() {
*/
ChainDB.prototype.getCoin = co(function* getCoin(hash, index) {
var coins = this.coinCache.get(hash);
var coins;
if (this.options.spv)
return;
coins = this.coinCache.get(hash);
if (coins)
return Coins.parseCoin(coins, hash, index);
@ -698,7 +704,12 @@ ChainDB.prototype.getCoin = co(function* getCoin(hash, index) {
*/
ChainDB.prototype.getCoins = co(function* getCoins(hash) {
var coins = this.coinCache.get(hash);
var coins;
if (this.options.spv)
return;
coins = this.coinCache.get(hash);
if (coins)
return Coins.fromRaw(coins, hash);
@ -776,10 +787,9 @@ ChainDB.prototype.getUndoCoins = co(function* getUndoCoins(hash) {
*/
ChainDB.prototype.getUndoView = co(function* getUndoView(block) {
var i, j, k, tx, input, coin, view, coins;
view = yield this.getCoinView(block);
coins = yield this.getUndoCoins(block.hash());
var view = yield this.getCoinView(block);
var coins = yield this.getUndoCoins(block.hash());
var i, j, k, tx, input, coin;
if (!coins)
return view;
@ -810,8 +820,12 @@ ChainDB.prototype.getUndoView = co(function* getUndoView(block) {
*/
ChainDB.prototype.getBlock = co(function* getBlock(hash) {
var item = yield this.getBoth(hash);
var data, block;
var item, data, block;
if (this.options.spv)
return;
item = yield this.getBoth(hash);
if (!item.hash)
return;
@ -821,12 +835,6 @@ ChainDB.prototype.getBlock = co(function* getBlock(hash) {
if (!data)
return;
if (this.options.spv) {
block = MerkleBlock.fromFull(data);
block.setHeight(item.height);
return block;
}
block = Block.fromRaw(data);
block.setHeight(item.height);
@ -840,7 +848,12 @@ ChainDB.prototype.getBlock = co(function* getBlock(hash) {
*/
ChainDB.prototype.getRawBlock = co(function* getRawBlock(block) {
var hash = yield this.getHash(block);
var hash;
if (this.options.spv)
return;
hash = yield this.getHash(block);
if (!hash)
return;
@ -857,9 +870,6 @@ ChainDB.prototype.getRawBlock = co(function* getRawBlock(block) {
ChainDB.prototype.getFullBlock = co(function* getFullBlock(hash) {
var block = yield this.getBlock(hash);
if (this.options.spv)
return block;
if (!block)
return;
@ -1090,7 +1100,7 @@ ChainDB.prototype.getTXByAddress = co(function* getTXByAddress(addresses) {
* Scan the blockchain for transactions containing specified address hashes.
* @param {Hash} start - Block hash to start at.
* @param {Hash[]} hashes - Address hashes.
* @param {Function} iter - Iterator. Accepts ({@link TX}, {@link Function}).
* @param {Function} iter - Iterator.
* @returns {Promise}
*/
@ -1358,6 +1368,28 @@ ChainDB.prototype.reset = co(function* reset(block) {
}
});
/**
* Reset the chain to a height or hash. Useful for replaying
* the blockchain download for SPV.
* @param {Hash|Number} block - hash/height
* @returns {Promise}
*/
ChainDB.prototype.replay = co(function* replay(block) {
var entry = yield this.get(block);
if (!entry)
throw new Error('Block not found.');
if (!(yield entry.isMainChain()))
throw new Error('Cannot reset on alternate chain.');
if (entry.hash === this.network.genesis.hash)
return yield this.reset(entry.hash);
yield this.reset(entry.prevBlock);
});
/**
* Save a block (not an entry) to the
* database and potentially connect the inputs.
@ -1367,11 +1399,8 @@ ChainDB.prototype.reset = co(function* reset(block) {
*/
ChainDB.prototype.saveBlock = co(function* saveBlock(block, view) {
if (this.options.spv) {
this.put(layout.b(block.hash()), block.toFull());
yield this.pruneBlock(block);
if (this.options.spv)
return;
}
this.put(layout.b(block.hash()), block.toRaw());
@ -1389,7 +1418,12 @@ ChainDB.prototype.saveBlock = co(function* saveBlock(block, view) {
*/
ChainDB.prototype.removeBlock = co(function* removeBlock(hash) {
var block = yield this.getBlock(hash);
var block;
if (this.options.spv)
return;
block = yield this.getBlock(hash);
if (!block)
throw new Error('Block not found.');
@ -1599,7 +1633,10 @@ ChainDB.prototype.disconnectBlock = co(function* disconnectBlock(block) {
ChainDB.prototype.pruneBlock = co(function* pruneBlock(block) {
var height, hash;
if (!this.options.prune && !this.options.spv)
if (this.options.spv)
return;
if (!this.options.prune)
return;
height = block.height - this.network.block.keepBlocks;

View File

@ -680,7 +680,7 @@ HTTPServer.prototype._init = function _init() {
var fee;
if (!this.fees)
return send(400, { error: 'Fee estimation not available.' });
return send(200, { rate: utils.btc(this.network.feeRate) });
fee = this.fees.estimateFee(req.options.blocks);
@ -695,7 +695,7 @@ HTTPServer.prototype._init = function _init() {
enforce(height != null, 'Hash or height is required.');
send(200, { success: true });
yield this.node.scan(height);
yield this.walletdb.rescan(height);
}));
// Resend
@ -1244,7 +1244,42 @@ HTTPServer.prototype._initIO = function _initIO() {
callback();
});
socket.on('scan chain', function(args, callback) {
socket.on('estimate fee', function(args, callback) {
var blocks = args[0];
var rate;
if (blocks != null && !utils.isNumber(blocks))
return callback({ error: 'Invalid parameter.' });
if (!self.fees) {
rate = self.network.feeRate;
rate = utils.btc(rate);
return callback(null, rate);
}
rate = self.fees.estimateFee(blocks);
rate = utils.btc(rate);
return callback(null, rate);
});
socket.on('send', function(args, callback) {
var data = args[0];
var tx;
if (!utils.isHex(data))
return callback({ error: 'Invalid parameter.' });
try {
tx = TX.fromRaw(data, 'hex');
} catch (e) {
return callback({ error: 'Invalid parameter.' });
}
self.node.send(tx);
});
socket.on('scan', function(args, callback) {
var start = args[0];
if (!utils.isHex256(start) && !utils.isUInt32(start))
@ -1402,6 +1437,7 @@ function ClientSocket(server, socket) {
this.filterCount = 0;
this.api = false;
this.node = this.server.node;
this.chain = this.server.chain;
this.mempool = this.server.mempool;
this.pool = this.server.pool;
@ -1574,7 +1610,7 @@ ClientSocket.prototype.testFilter = function testFilter(tx) {
ClientSocket.prototype.scan = co(function* scan(start) {
var scanner = this.scanner.bind(this);
yield this.chain.db.scan(start, this.filter, scanner);
yield this.node.scan(start, this.filter, scanner);
});
ClientSocket.prototype.scanner = function scanner(entry, txs) {

View File

@ -28,7 +28,7 @@ try {
/**
* Create a fullnode complete with a chain,
* mempool, miner, wallet, etc.
* @exports Fullnode
* @exports FullNode
* @extends Node
* @constructor
* @param {Object?} options
@ -53,15 +53,15 @@ try {
* @property {Miner} miner
* @property {WalletDB} walletdb
* @property {HTTPServer} http
* @emits Fullnode#block
* @emits Fullnode#tx
* @emits Fullnode#alert
* @emits Fullnode#error
* @emits FullNode#block
* @emits FullNode#tx
* @emits FullNode#alert
* @emits FullNode#error
*/
function Fullnode(options) {
if (!(this instanceof Fullnode))
return new Fullnode(options);
function FullNode(options) {
if (!(this instanceof FullNode))
return new FullNode(options);
Node.call(this, options);
@ -143,7 +143,7 @@ function Fullnode(options) {
this.walletdb = new WalletDB({
network: this.network,
logger: this.logger,
fees: this.fees,
client: this,
db: this.options.db,
location: this.location('walletdb'),
witness: this.options.witness,
@ -175,14 +175,14 @@ function Fullnode(options) {
this._init();
}
utils.inherits(Fullnode, Node);
utils.inherits(FullNode, Node);
/**
* Initialize the node.
* @private
*/
Fullnode.prototype._init = function _init() {
FullNode.prototype._init = function _init() {
var self = this;
var onError = this._error.bind(this);
@ -227,20 +227,16 @@ Fullnode.prototype._init = function _init() {
this.miner.on('block', function(block) {
self.broadcast(block.toInv()).catch(onError);
});
this.walletdb.on('send', function(tx) {
self.sendTX(tx).catch(onError);
});
};
/**
* Open the node and all its child objects,
* wait for the database to load.
* @alias Fullnode#open
* @alias FullNode#open
* @returns {Promise}
*/
Fullnode.prototype._open = co(function* open() {
FullNode.prototype._open = co(function* open() {
yield this.chain.open();
yield this.mempool.open();
yield this.miner.open();
@ -250,12 +246,6 @@ Fullnode.prototype._open = co(function* open() {
// Ensure primary wallet.
yield this.openWallet();
// Rescan for any missed transactions.
yield this.rescan();
// Rebroadcast pending transactions.
yield this.resend();
if (this.http)
yield this.http.open();
@ -264,11 +254,11 @@ Fullnode.prototype._open = co(function* open() {
/**
* Close the node, wait for the database to close.
* @alias Fullnode#close
* @alias FullNode#close
* @returns {Promise}
*/
Fullnode.prototype._close = co(function* close() {
FullNode.prototype._close = co(function* close() {
if (this.http)
yield this.http.close();
@ -286,25 +276,35 @@ Fullnode.prototype._close = co(function* close() {
});
/**
* Rescan for any missed transactions.
* Watch address hashes (nop).
* @param {Hash[]} hashes
* @returns {Promise}
*/
Fullnode.prototype.rescan = function rescan() {
// Always rescan to make sure we didn't
// miss anything: there is no atomicity
// between the chaindb and walletdb.
return this.scan();
FullNode.prototype.watchAddress = function watchAddress(hashes) {
return Promise.resolve();
};
/**
* Rescan for any missed transactions.
* @param {Number} height
* @param {Hash} start - Block hash to start at.
* @param {Hash[]} hashes - Address hashes.
* @param {Function} iter - Iterator.
* @returns {Promise}
*/
Fullnode.prototype.scan = function scan(height) {
return this.walletdb.rescan(this.chain.db, height);
FullNode.prototype.scan = function scan(start, filter, iter) {
return this.chain.db.scan(start, filter, iter);
};
/**
* Esimate smart fee.
* @param {Number?} blocks
* @returns {Promise}
*/
FullNode.prototype.estimateFee = function estimateFee(blocks) {
return Promise.resolve(this.fees.estimateFee(blocks));
};
/**
@ -315,20 +315,20 @@ Fullnode.prototype.scan = function scan(height) {
* @returns {Promise}
*/
Fullnode.prototype.broadcast = function broadcast(item, callback) {
FullNode.prototype.broadcast = function broadcast(item, callback) {
return this.pool.broadcast(item, callback);
};
/**
* Verify a transaction, add it to the mempool, and broadcast.
* Safer than {@link Fullnode#broadcast}.
* Safer than {@link FullNode#broadcast}.
* @example
* node.sendTX(tx, callback);
* node.sendTX(tx, true, callback);
* @param {TX} tx
*/
Fullnode.prototype.sendTX = co(function* sendTX(tx) {
FullNode.prototype.sendTX = co(function* sendTX(tx) {
try {
yield this.mempool.addTX(tx);
} catch (err) {
@ -353,7 +353,7 @@ Fullnode.prototype.sendTX = co(function* sendTX(tx) {
* the p2p network (accepts leech peers).
*/
Fullnode.prototype.listen = function listen() {
FullNode.prototype.listen = function listen() {
return this.pool.listen();
};
@ -361,7 +361,7 @@ Fullnode.prototype.listen = function listen() {
* Connect to the network.
*/
Fullnode.prototype.connect = function connect() {
FullNode.prototype.connect = function connect() {
return this.pool.connect();
};
@ -369,7 +369,7 @@ Fullnode.prototype.connect = function connect() {
* Start the blockchain sync.
*/
Fullnode.prototype.startSync = function startSync() {
FullNode.prototype.startSync = function startSync() {
return this.pool.startSync();
};
@ -377,7 +377,7 @@ Fullnode.prototype.startSync = function startSync() {
* Stop syncing the blockchain.
*/
Fullnode.prototype.stopSync = function stopSync() {
FullNode.prototype.stopSync = function stopSync() {
return this.pool.stopSync();
};
@ -387,7 +387,7 @@ Fullnode.prototype.stopSync = function stopSync() {
* @returns {Promise} - Returns {@link Block}.
*/
Fullnode.prototype.getBlock = function getBlock(hash) {
FullNode.prototype.getBlock = function getBlock(hash) {
return this.chain.db.getBlock(hash);
};
@ -397,7 +397,7 @@ Fullnode.prototype.getBlock = function getBlock(hash) {
* @returns {Promise} - Returns {@link Block}.
*/
Fullnode.prototype.getFullBlock = function getFullBlock(hash) {
FullNode.prototype.getFullBlock = function getFullBlock(hash) {
return this.chain.db.getFullBlock(hash);
};
@ -409,7 +409,7 @@ Fullnode.prototype.getFullBlock = function getFullBlock(hash) {
* @returns {Promise} - Returns {@link Coin}.
*/
Fullnode.prototype.getCoin = function getCoin(hash, index) {
FullNode.prototype.getCoin = function getCoin(hash, index) {
var coin = this.mempool.getCoin(hash, index);
if (coin)
@ -428,7 +428,7 @@ Fullnode.prototype.getCoin = function getCoin(hash, index) {
* @returns {Promise} - Returns {@link Coin}[].
*/
Fullnode.prototype.getCoinsByAddress = co(function* getCoinsByAddress(addresses) {
FullNode.prototype.getCoinsByAddress = co(function* getCoinsByAddress(addresses) {
var coins = this.mempool.getCoinsByAddress(addresses);
var i, blockCoins, coin, spent;
@ -452,7 +452,7 @@ Fullnode.prototype.getCoinsByAddress = co(function* getCoinsByAddress(addresses)
* @returns {Promise} - Returns {@link TX}[].
*/
Fullnode.prototype.getTXByAddress = co(function* getTXByAddress(addresses) {
FullNode.prototype.getTXByAddress = co(function* getTXByAddress(addresses) {
var mempool = this.mempool.getTXByAddress(addresses);
var txs = yield this.chain.db.getTXByAddress(addresses);
return mempool.concat(txs);
@ -464,7 +464,7 @@ Fullnode.prototype.getTXByAddress = co(function* getTXByAddress(addresses) {
* @returns {Promise} - Returns {@link TX}.
*/
Fullnode.prototype.getTX = function getTX(hash) {
FullNode.prototype.getTX = function getTX(hash) {
var tx = this.mempool.getTX(hash);
if (tx)
@ -479,7 +479,7 @@ Fullnode.prototype.getTX = function getTX(hash) {
* @returns {Promise} - Returns Boolean.
*/
Fullnode.prototype.hasTX = function hasTX(hash) {
FullNode.prototype.hasTX = function hasTX(hash) {
if (this.mempool.hasTX(hash))
return Promise.resolve(true);
@ -493,7 +493,7 @@ Fullnode.prototype.hasTX = function hasTX(hash) {
* @returns {Promise} - Returns Boolean.
*/
Fullnode.prototype.isSpent = function isSpent(hash, index) {
FullNode.prototype.isSpent = function isSpent(hash, index) {
if (this.mempool.isSpent(hash, index))
return Promise.resolve(true);
@ -507,7 +507,7 @@ Fullnode.prototype.isSpent = function isSpent(hash, index) {
* @returns {Promise} - Returns {@link TX}.
*/
Fullnode.prototype.fillCoins = function fillCoins(tx) {
FullNode.prototype.fillCoins = function fillCoins(tx) {
return this.mempool.fillAllCoins(tx);
};
@ -518,7 +518,7 @@ Fullnode.prototype.fillCoins = function fillCoins(tx) {
* @returns {Promise} - Returns {@link TX}.
*/
Fullnode.prototype.fillHistory = function fillHistory(tx) {
FullNode.prototype.fillHistory = function fillHistory(tx) {
return this.mempool.fillAllHistory(tx);
};
@ -528,7 +528,7 @@ Fullnode.prototype.fillHistory = function fillHistory(tx) {
* @returns {Promise} - Returns {@link Confidence}.
*/
Fullnode.prototype.getConfidence = function getConfidence(tx) {
FullNode.prototype.getConfidence = function getConfidence(tx) {
return this.mempool.getConfidence(tx);
};
@ -536,4 +536,4 @@ Fullnode.prototype.getConfidence = function getConfidence(tx) {
* Expose
*/
module.exports = Fullnode;
module.exports = FullNode;

View File

@ -263,12 +263,26 @@ Node.prototype.openWallet = co(function* openWallet() {
});
/**
* Resend all pending transactions.
* Get chain tip.
* @returns {Promise}
*/
Node.prototype.resend = function resend() {
return this.walletdb.resend();
Node.prototype.getTip = function getTip() {
return Promise.resolve(this.chain.tip);
};
/**
* Send a transaction. Do not wait for promise.
* @param {TX} tx
* @returns {Promise}
*/
Node.prototype.send = function send(tx) {
var onError = this._error.bind(this);
this.sendTX(tx).catch(onError);
return Promise.resolve();
};
/*

View File

@ -83,6 +83,7 @@ function SPVNode(options) {
this.walletdb = new WalletDB({
network: this.network,
logger: this.logger,
client: this,
db: this.options.db,
location: this.location('walletdb'),
witness: this.options.witness,
@ -151,14 +152,6 @@ SPVNode.prototype._init = function _init() {
this.chain.on('disconnect', function(entry, block) {
self.walletdb.removeBlock(entry).catch(onError);
});
this.walletdb.on('path', function(path) {
self.pool.watch(path.hash, 'hex');
});
this.walletdb.on('send', function(tx) {
self.sendTX(tx).catch(onError);
});
};
/**
@ -179,12 +172,6 @@ SPVNode.prototype._open = co(function* open(callback) {
// Load bloom filter.
yield this.openFilter();
// Rescan for any missed transactions.
yield this.rescan();
// Rebroadcast pending transactions.
yield this.resend();
if (this.http)
yield this.http.open();
@ -211,44 +198,41 @@ SPVNode.prototype._close = co(function* close() {
});
/**
* Initialize p2p bloom filter for address watching.
* Watch address hashes.
* @param {Hash[]} hashes
* @returns {Promise}
*/
SPVNode.prototype.openFilter = co(function* openFilter() {
var hashes = yield this.walletdb.getAddressHashes();
SPVNode.prototype.watchAddress = function watchAddress(hashes) {
var i;
if (hashes.length > 0)
this.logger.info('Adding %d addresses to filter.', hashes.length);
this.logger.info('Adding %d addresses to filter.', hashes.length);
for (i = 0; i < hashes.length; i++)
this.pool.watch(hashes[i], 'hex');
});
/**
* Rescan for any missed transactions.
* Note that this will replay the blockchain sync.
* @returns {Promise}
*/
SPVNode.prototype.rescan = function rescan() {
// Always replay the last block to make
// sure we didn't miss anything: there
// is no atomicity between the chaindb
// and walletdb.
return this.scan();
return Promise.resolve();
};
/**
* Scan for any missed transactions.
* Note that this will replay the blockchain sync.
* @param {Number|Hash} height
* @param {Number|Hash} start
* @returns {Promise}
*/
SPVNode.prototype.scan = function scan(height) {
return this.walletdb.rescan(this.chain.db, height);
SPVNode.prototype.scan = function rescan(start) {
return this.chain.db.replay(start);
};
/**
* Estimate smart fee (returns network fee rate).
* @param {Number?} blocks
* @returns {Promise}
*/
SPVNode.prototype.estimateFee = function estimateFee(blocks) {
return Promise.resolve(this.network.feeRate);
};
/**

View File

@ -410,82 +410,6 @@ MerkleBlock.fromRaw = function fromRaw(data, enc) {
return new MerkleBlock().fromRaw(data);
};
/**
* Serialize the merkleblock.
* @param {String?} enc - Encoding, can be `'hex'` or null.
* @returns {Buffer|String}
*/
MerkleBlock.prototype.toFull = function toFull(writer) {
var p = BufferWriter(writer);
var i, tx, index;
this.toRaw(p);
p.writeVarint(this.txs.length);
for (i = 0; i < this.txs.length; i++) {
tx = this.txs[i];
index = tx.index;
if (index === -1)
index = 0x7fffffff;
p.writeU32(index);
tx.toRaw(p);
}
if (!writer)
p = p.render();
return p;
};
/**
* Inject properties from serialized data.
* @private
* @param {Buffer} data
*/
MerkleBlock.prototype.fromFull = function fromFull(data) {
var p = BufferReader(data);
var i, count, index, tx, hash, txid;
this.fromRaw(p);
this._validPartial = true;
count = p.readVarint();
for (i = 0; i < count; i++) {
index = p.readU32();
if (index === 0x7fffffff)
index = -1;
tx = TX.fromRaw(p);
hash = tx.hash();
txid = tx.hash('hex');
tx.setBlock(this, index);
this.txs.push(tx);
if (index !== -1) {
this.matches.push(hash);
this.map[txid] = index;
}
}
return this;
};
/**
* Instantiate a merkleblock from a serialized data.
* @param {Buffer} data
* @param {String?} enc - Encoding, can be `'hex'` or null.
* @returns {MerkleBlock}
*/
MerkleBlock.fromFull = function fromFull(data, enc) {
if (typeof data === 'string')
data = new Buffer(data, enc);
return new MerkleBlock().fromFull(data);
};
/**
* Convert the block to an object suitable
* for JSON serialization. Note that the hashes

View File

@ -905,14 +905,6 @@ TXDB.prototype.removeTXRecord = co(function* removeTXRecord(tx) {
if (!map.remove(this.wallet.wid))
return;
if (tx.height !== -1) {
block = yield this.walletdb.getBlockMap(tx.height);
assert(block);
if (block.remove(hash, this.wallet.wid))
this.walletdb.writeBlockMap(this.wallet, tx.height, block);
}
if (map.wids.length === 0) {
this.walletdb.unwriteTXMap(this.wallet, hash);
return;
@ -1500,6 +1492,9 @@ TXDB.prototype.erase = co(function* erase(tx) {
yield this.removeTXRecord(tx);
if (tx.height !== -1)
yield this.removeBlockRecord(tx, tx.height);
// Update the transaction counter
// and commit new state due to
// balance change.

View File

@ -1366,14 +1366,10 @@ Wallet.prototype._fund = co(function* fund(tx, options) {
coins = yield this.getCoins(options.account);
rate = this.network.feeRate;
rate = options.rate;
if (options.rate != null) {
rate = options.rate;
} else {
if (this.db.fees)
rate = this.db.fees.estimateFee();
}
if (rate == null)
rate = yield this.db.estimateFee();
// Don't use any locked coins.
coins = this.txdb.filterLocked(coins);
@ -1486,7 +1482,8 @@ Wallet.prototype._send = co(function* send(options, passphrase) {
yield this.db.addTX(tx);
this.logger.debug('Sending wallet tx (%s): %s', this.id, tx.rhash);
this.db.emit('send', tx);
yield this.db.send(tx);
return tx;
});
@ -1504,7 +1501,7 @@ Wallet.prototype.resend = co(function* resend() {
this.logger.info('Rebroadcasting %d transactions.', txs.length);
for (i = 0; i < txs.length; i++)
this.db.emit('send', txs[i]);
yield this.db.send(tx);
return txs;
});

View File

@ -162,10 +162,9 @@ function WalletDB(options) {
AsyncObject.call(this);
this.options = options;
this.prune = options.prune !== false;
this.network = Network.get(options.network);
this.fees = options.fees;
this.logger = options.logger || Logger.global;
this.client = options.client;
this.tip = null;
this.height = -1;
@ -242,6 +241,8 @@ WalletDB.prototype._open = co(function* open() {
this.depth, this.height);
yield this.loadFilter();
yield this.connect();
yield this.resend();
});
/**
@ -263,6 +264,175 @@ WalletDB.prototype._close = co(function* close() {
yield this.db.close();
});
/**
* Connect and sync with the chain server.
* @returns {Promise}
*/
WalletDB.prototype.connect = co(function* connect() {
var unlock = yield this.txLock.lock();
try {
return yield this._connect();
} finally {
unlock();
}
});
/**
* Connect and sync with the chain server (without a lock).
* @private
* @returns {Promise}
*/
WalletDB.prototype._connect = co(function* connect() {
var hashes, tip, height;
if (!this.client)
return;
// yield this.client.connect();
// yield this.client.watchChain();
hashes = yield this.getHashes();
yield this.watchAddress(hashes);
if (this.options.noScan) {
tip = yield this.client.getTip();
if (!tip)
throw new Error('Could not get chain tip.');
yield this.forceTip(tip);
return;
}
assert(this.network.block.keepBlocks > 36);
height = this.height - 36;
if (height < 0)
height = 0;
yield this.scan(height, hashes);
});
/**
* Force a rescan.
* @param {ChainClient} chain
* @param {Number} height
* @returns {Promise}
*/
WalletDB.prototype.rescan = co(function* rescan(height) {
var unlock = yield this.txLock.lock();
try {
return yield this._rescan(height);
} finally {
unlock();
}
});
/**
* Force a rescan (without a lock).
* @private
* @param {Number} height
* @returns {Promise}
*/
WalletDB.prototype._rescan = co(function* rescan(height) {
var hashes;
if (!this.client)
return;
assert(utils.isNumber(height), 'Must pass in a height.');
hashes = yield this.getHashes();
yield this.watchAddress(hashes);
yield this.scan(height, hashes);
});
/**
* Sync with the chain server (without a lock).
* @private
* @param {Number} height
* @param {Hashes[]} hashes
* @returns {Promise}
*/
WalletDB.prototype.scan = co(function* sync(height, hashes) {
var self = this;
var blocks;
if (!this.client)
return;
assert(utils.isNumber(height), 'Must pass in a height.');
blocks = this.height - height;
if (blocks < 0)
throw new Error('Cannot rescan future blocks.');
if (blocks > this.network.block.keepBlocks)
throw new Error('Cannot roll back beyond keepBlocks.');
yield this.rollback(height);
this.logger.info('Scanning for %d addresses.', hashes.length);
yield this.client.scan(this.tip.hash, hashes, function(block, txs) {
return self._addBlock(block, txs);
});
});
/**
* Add addresses to chain server filter.
* @param {Hashes[]} hashes
* @returns {Promise}
*/
WalletDB.prototype.watchAddress = co(function* watchAddress(hashes) {
if (!this.client) {
this.emit('watch address', hashes);
return;
}
yield this.client.watchAddress(hashes);
});
/**
* Broadcast a transaction via chain server.
* @param {TX} tx
* @returns {Promise}
*/
WalletDB.prototype.send = co(function* send(tx) {
if (!this.client) {
this.emit('send', tx);
return;
}
yield this.client.send(tx);
});
/**
* Estimate smart fee from chain server.
* @param {Number} blocks
* @returns {Promise}
*/
WalletDB.prototype.estimateFee = co(function* estimateFee(blocks) {
if (!this.client)
return this.network.feeRate;
return yield this.client.estimateFee(blocks);
});
/**
* Backup the wallet db.
* @param {String} path
@ -991,7 +1161,7 @@ WalletDB.prototype.savePath = co(function* savePath(wallet, path) {
this.addFilter(hash);
this.emit('path', path);
yield this.watchAddress([path.hash]);
map = yield this.getPathMap(hash);
@ -1173,71 +1343,6 @@ WalletDB.prototype.decryptKeys = co(function* decryptKeys(wallet, key) {
}
});
/**
* Rescan the blockchain.
* @param {ChainDB} chaindb
* @param {Number} height
* @returns {Promise}
*/
WalletDB.prototype.rescan = co(function* rescan(chaindb, height) {
var unlock = yield this.txLock.lock();
try {
return yield this._rescan(chaindb, height);
} finally {
unlock();
}
});
/**
* Rescan the blockchain without a lock.
* @private
* @param {ChainDB} chaindb
* @param {Number} height
* @returns {Promise}
*/
WalletDB.prototype._rescan = co(function* rescan(chaindb, height) {
var self = this;
var tip, hashes, blocks;
if (this.options.noScan) {
tip = yield chaindb.getTip();
if (!tip)
throw new Error('Could not get chain tip.');
yield this.forceTip(tip);
return;
}
if (height == null) {
assert(this.network.block.keepBlocks > 36);
height = this.height - 36;
if (height < 0)
height = 0;
}
blocks = this.height - height;
if (blocks < 0)
throw new Error('Cannot rescan future blocks.');
if (blocks > this.network.block.keepBlocks)
throw new Error('Cannot roll back beyond keepBlocks.');
yield this.rollback(height);
hashes = yield this.getHashes();
this.logger.info('Scanning for %d addresses.', hashes.length);
yield chaindb.scan(this.tip.hash, hashes, function(block, txs) {
return self._addBlock(block, txs);
});
});
/**
* Get keys of all pending transactions
* in the wallet db (for resending).
@ -1351,7 +1456,7 @@ WalletDB.prototype.resend = co(function* resend() {
if (tx.isCoinbase())
continue;
this.emit('send', tx);
yield this.send(tx);
}
});