walletdb: start separating out walletdb.
This commit is contained in:
parent
ffc17b48c2
commit
3b333c07cd
@ -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;
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@ -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);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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;
|
||||
});
|
||||
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user