refactor. allow tx querying by timestamp.

This commit is contained in:
Christopher Jeffrey 2016-03-04 03:49:43 -08:00
parent e962686e93
commit f67902a9cf
7 changed files with 362 additions and 270 deletions

View File

@ -68,11 +68,7 @@ Address.prototype.getID = function getID() {
Address.prototype.addKey = function addKey(key) { Address.prototype.addKey = function addKey(key) {
key = utils.ensureBuffer(key); key = utils.ensureBuffer(key);
var has = this.keys.some(function(k) { if (utils.indexOf(this.keys, key) !== -1)
return utils.isEqual(k, key);
});
if (has)
return; return;
this.keys.push(key); this.keys.push(key);
@ -81,15 +77,13 @@ Address.prototype.addKey = function addKey(key) {
}; };
Address.prototype.removeKey = function removeKey(key) { Address.prototype.removeKey = function removeKey(key) {
var index;
key = utils.ensureBuffer(key); key = utils.ensureBuffer(key);
var index = this.keys.map(function(k, i) { index = utils.indexOf(this.keys, key);
return utils.isEqual(k, key) ? i : null;
}).filter(function(i) {
return i !== null;
})[0];
if (index == null) if (index === -1)
return; return;
this.keys.splice(index, 1); this.keys.splice(index, 1);
@ -175,7 +169,8 @@ Address.prototype.getProgramAddress = function getProgramAddress() {
if (this._programAddress) if (this._programAddress)
return this._programAddress; return this._programAddress;
this._programAddress = Address.compileHash(this.getProgramHash(), 'scripthash'); this._programAddress =
Address.compileHash(this.getProgramHash(), 'scripthash');
return this._programAddress; return this._programAddress;
}; };
@ -215,10 +210,13 @@ Address.prototype.getScriptAddress = function getScriptAddress() {
if (this._scriptAddress) if (this._scriptAddress)
return this._scriptAddress; return this._scriptAddress;
if (this.witness) if (this.witness) {
this._scriptAddress = Address.compileHash(this.getScriptHash256(), 'witnessscripthash'); this._scriptAddress =
else Address.compileHash(this.getScriptHash256(), 'witnessscripthash');
this._scriptAddress = Address.compileHash(this.getScriptHash160(), 'scripthash'); } else {
this._scriptAddress =
Address.compileHash(this.getScriptHash160(), 'scripthash');
}
return this._scriptAddress; return this._scriptAddress;
}; };
@ -273,57 +271,26 @@ Address.prototype._getAddressMap = function _getAddressMap() {
return this.addressMap; return this.addressMap;
}; };
Address.prototype.ownOutput = function ownOutput(tx, index) {
var addressMap = this._getAddressMap();
var outputs = tx.outputs;
if ((tx instanceof bcoin.output) || (tx instanceof bcoin.coin)) {
outputs = [tx];
tx = null;
}
outputs = outputs.filter(function(output, i) {
if (index != null && index !== i)
return false;
return output.test(addressMap);
}, this);
if (outputs.length === 0)
return false;
return outputs;
};
Address.prototype.ownInput = function ownInput(tx, index) { Address.prototype.ownInput = function ownInput(tx, index) {
var addressMap = this._getAddressMap(); var addressMap = this._getAddressMap();
var inputs = tx.inputs;
if (tx instanceof bcoin.input) { if (tx instanceof bcoin.input)
inputs = [tx]; return tx.test(addressMap);
tx = null;
}
inputs = inputs.filter(function(input, i) { return tx.testInputs(addressMap, index);
if (index != null && index !== i) };
return false;
if (input.output) Address.prototype.ownOutput = function ownOutput(tx, index) {
return input.output.test(addressMap); var addressMap = this._getAddressMap();
return input.test(addressMap); if (tx instanceof bcoin.output)
}, this); return tx.test(addressMap);
if (inputs.length === 0) return tx.testOutputs(addressMap, index);
return false;
return inputs;
}; };
Address.prototype.scriptInputs = function scriptInputs(tx, index) { Address.prototype.scriptInputs = function scriptInputs(tx, index) {
var self = this; var self = this;
var publicKey = this.getPublicKey();
var redeem = this.getScript();
if (index && typeof index === 'object') if (index && typeof index === 'object')
index = tx.inputs.indexOf(index); index = tx.inputs.indexOf(index);
@ -347,13 +314,11 @@ Address.prototype.scriptInputs = function scriptInputs(tx, index) {
Address.prototype.signInputs = function signInputs(tx, type, index) { Address.prototype.signInputs = function signInputs(tx, type, index) {
var self = this; var self = this;
var key = this.key;
var total = 0;
if (index && typeof index === 'object') if (index && typeof index === 'object')
index = tx.inputs.indexOf(index); index = tx.inputs.indexOf(index);
if (!key.privateKey) if (!this.key.privateKey)
return 0; return 0;
return tx.inputs.reduce(function(total, input, i) { return tx.inputs.reduce(function(total, input, i) {
@ -375,13 +340,11 @@ Address.prototype.signInputs = function signInputs(tx, type, index) {
Address.prototype.sign = function sign(tx, type, index) { Address.prototype.sign = function sign(tx, type, index) {
var self = this; var self = this;
var redeem = this.getScript();
var key = this.key;
if (index && typeof index === 'object') if (index && typeof index === 'object')
index = tx.inputs.indexOf(index); index = tx.inputs.indexOf(index);
if (!key.privateKey) if (!this.key.privateKey)
return 0; return 0;
// Add signature script to each input // Add signature script to each input

View File

@ -201,9 +201,9 @@ HTTPServer.prototype._init = function _init() {
// Update wallet / sync address depth // Update wallet / sync address depth
this.put('/wallet/:id', function(req, res, next, send) { this.put('/wallet/:id', function(req, res, next, send) {
var id = req.params.id; var id = req.params.id;
var receive = req.body.receiveDepth; var receive = req.body.receiveDepth >>> 0;
var change = req.body.changeDepth; var change = req.body.changeDepth >>> 0;
self.node.walletdb.syncDepth(id, receive, change, function(err) { self.node.walletdb.setDepth(id, receive, change, function(err) {
if (err) if (err)
return next(err); return next(err);

View File

@ -117,6 +117,10 @@ MerkleBlock.prototype._verify = function _verify() {
return true; return true;
}; };
MerkleBlock.prototype.getCoinbaseHeight = function getCoinbaseHeight() {
return -1;
};
MerkleBlock.prototype.inspect = function inspect() { MerkleBlock.prototype.inspect = function inspect() {
var copy = bcoin.merkleblock(this); var copy = bcoin.merkleblock(this);
copy.__proto__ = null; copy.__proto__ = null;

View File

@ -1282,7 +1282,7 @@ Pool.prototype.searchWallet = function(wallet, callback) {
if (!this.options.spv) if (!this.options.spv)
return callback(); return callback();
wallet.getLast(function(err, ts, height) { wallet.getLastTime(function(err, ts, height) {
if (err) if (err)
return callback(err); return callback(err);

View File

@ -76,7 +76,9 @@ TXPool.prototype._lock = function _lock(func, args, force) {
TXPool.prototype.getMap = function getMap(tx, callback) { TXPool.prototype.getMap = function getMap(tx, callback) {
var self = this; var self = this;
var addresses = tx.getAddresses(); var input = tx.getInputAddresses();
var output = tx.getOutputAddresses();
var addresses = utils.uniqs(input.concat(output));
var map; var map;
function cb(err, map) { function cb(err, map) {
@ -87,12 +89,12 @@ TXPool.prototype.getMap = function getMap(tx, callback) {
map.output = []; map.output = [];
map.all = []; map.all = [];
tx.getInputAddresses().forEach(function(address) { input.forEach(function(address) {
assert(map[address]); assert(map[address]);
map.input = map.input.concat(map[address]); map.input = map.input.concat(map[address]);
}); });
tx.getOutputAddresses().forEach(function(address) { output.forEach(function(address) {
assert(map[address]); assert(map[address]);
map.output = map.output.concat(map[address]); map.output = map.output.concat(map[address]);
}); });
@ -122,7 +124,6 @@ TXPool.prototype.mapAddresses = function mapAddresses(address, callback) {
var iter; var iter;
if (Array.isArray(address)) { if (Array.isArray(address)) {
address = utils.uniqs(address);
return utils.forEachSerial(address, function(address, next) { return utils.forEachSerial(address, function(address, next) {
self.mapAddresses(address, function(err, res) { self.mapAddresses(address, function(err, res) {
if (err) if (err)
@ -166,7 +167,6 @@ TXPool.prototype.mapAddresses = function mapAddresses(address, callback) {
return iter.end(function(err) { return iter.end(function(err) {
if (err) if (err)
return callback(err); return callback(err);
map[address] = utils.uniqs(map[address]);
return callback(null, map); return callback(null, map);
}); });
} }
@ -261,6 +261,13 @@ TXPool.prototype.add = function add(tx, callback) {
}); });
}; };
function pad32(num) {
num = num + '';
while (num.length < 10)
num = '0' + num;
return num;
}
// This big scary function is what a persistent tx pool // This big scary function is what a persistent tx pool
// looks like. It's a semi mempool in that it can handle // looks like. It's a semi mempool in that it can handle
// receiving txs out of order. // receiving txs out of order.
@ -269,10 +276,10 @@ TXPool.prototype._add = function add(tx, map, callback, force) {
var prefix = this.prefix + '/'; var prefix = this.prefix + '/';
var hash = tx.hash('hex'); var hash = tx.hash('hex');
var updated = false; var updated = false;
var own = false;
var uniq = {};
var batch; var batch;
assert(tx.ts > 0 || tx.ps > 0);
var unlock = this._lock(add, [tx, map, callback], force); var unlock = this._lock(add, [tx, map, callback], force);
if (!unlock) if (!unlock)
return; return;
@ -290,38 +297,29 @@ TXPool.prototype._add = function add(tx, map, callback, force) {
batch = self.db.batch(); batch = self.db.batch();
if (existing) { if (existing) {
own = true;
// Tricky - update the tx and coin in storage, // Tricky - update the tx and coin in storage,
// and remove pending flag to mark as confirmed. // and remove pending flag to mark as confirmed.
if (existing.ts === 0 && tx.ts !== 0) { if (existing.ts === 0 && tx.ts !== 0) {
assert(tx.height >= 0);
assert(existing.ps > 0);
batch.put(prefix + 't/t/' + hash, tx.toExtended()); batch.put(prefix + 't/t/' + hash, tx.toExtended());
batch.put(prefix + 't/h/' + tx.height + '/' + hash, DUMMY); batch.put(prefix + 't/h/h/' + pad32(tx.height) + '/' + hash, DUMMY);
batch.del(prefix + 'p/t/' + hash); batch.del(prefix + 't/s/s/' + pad32(existing.ps) + '/' + hash);
batch.put(prefix + 't/s/s/' + pad32(tx.ts) + '/' + hash, DUMMY);
batch.del(prefix + 't/p/t/' + hash);
tx.inputs.forEach(function(input) { map.all.forEach(function(id) {
var address = input.getAddress(); batch.put(
prefix + 't/h/a/' + id + '/' + pad32(tx.height) + '/' + hash, DUMMY);
if (input.isCoinbase()) batch.del(
return; prefix + 't/s/a/' + id + '/' + pad32(existing.ps) + '/' + hash);
batch.put(
if (address && !uniq[address]) { prefix + 't/s/a/' + id + '/' + pad32(tx.ts) + '/' + hash, DUMMY);
uniq[address] = true; batch.del(prefix + 't/p/a/' + id + '/' + hash);
map[address].forEach(function(id) {
batch.del(prefix + 'p/a/' + id + '/' + hash);
});
}
}); });
utils.forEachSerial(tx.outputs, function(output, next, i) { utils.forEachSerial(tx.outputs, function(output, next, i) {
var address = output.getAddress();
if (address && !uniq[address]) {
uniq[address] = true;
map[address].forEach(function(id) {
batch.del(prefix + 'p/a/' + id + '/' + hash);
});
}
self.getCoin(hash, i, function(err, coin) { self.getCoin(hash, i, function(err, coin) {
if (err) if (err)
return next(err); return next(err);
@ -355,6 +353,31 @@ TXPool.prototype._add = function add(tx, map, callback, force) {
return done(null, false); return done(null, false);
} }
batch.put(prefix + 't/t/' + hash, tx.toExtended());
if (tx.ts === 0) {
assert(tx.ps > 0);
batch.put(prefix + 't/p/t/' + hash, DUMMY);
batch.put(prefix + 't/s/s/' + pad32(tx.ps) + '/' + hash, DUMMY);
} else {
batch.put(prefix + 't/h/h/' + pad32(tx.height) + '/' + hash, DUMMY);
batch.put(prefix + 't/s/s/' + pad32(tx.ts) + '/' + hash, DUMMY);
}
map.all.forEach(function(id) {
batch.put(prefix + 't/a/' + id + '/' + hash, DUMMY);
if (tx.ts === 0) {
batch.put(prefix + 't/p/a/' + id + '/' + hash, DUMMY);
batch.put(
prefix + 't/s/a/' + id + '/' + pad32(tx.ps) + '/' + hash, DUMMY);
} else {
batch.put(
prefix + 't/h/a/' + id + '/' + pad32(tx.height) + '/' + hash, DUMMY);
batch.put(
prefix + 't/s/a/' + id + '/' + pad32(tx.ts) + '/' + hash, DUMMY);
}
});
// Consume unspent money or add orphans // Consume unspent money or add orphans
utils.forEachSerial(tx.inputs, function(input, next, i) { utils.forEachSerial(tx.inputs, function(input, next, i) {
var key; var key;
@ -372,15 +395,6 @@ TXPool.prototype._add = function add(tx, map, callback, force) {
address = input.getAddress(); address = input.getAddress();
if (address && !uniq[address]) {
uniq[address] = true;
map[address].forEach(function(id) {
batch.put(prefix + 't/a/' + id + '/' + hash, DUMMY);
if (tx.ts === 0)
batch.put(prefix + 'p/a/' + id + '/' + hash, DUMMY);
});
}
if (coin) { if (coin) {
// Add TX to inputs and spend money // Add TX to inputs and spend money
input.output = coin; input.output = coin;
@ -393,7 +407,6 @@ TXPool.prototype._add = function add(tx, map, callback, force) {
return done(null, false); return done(null, false);
updated = true; updated = true;
own = true;
if (address) { if (address) {
map[address].forEach(function(id) { map[address].forEach(function(id) {
@ -416,8 +429,6 @@ TXPool.prototype._add = function add(tx, map, callback, force) {
if (!address || !map[address].length) if (!address || !map[address].length)
return next(); return next();
own = true;
self.getTX(input.prevout.hash, function(err, result) { self.getTX(input.prevout.hash, function(err, result) {
if (err) if (err)
return done(err); return done(err);
@ -452,7 +463,6 @@ TXPool.prototype._add = function add(tx, map, callback, force) {
key = hash + '/' + i; key = hash + '/' + i;
coin = bcoin.coin(tx, i); coin = bcoin.coin(tx, i);
own = true;
self._getOrphans(key, function(err, orphans) { self._getOrphans(key, function(err, orphans) {
var some = false; var some = false;
@ -504,15 +514,6 @@ TXPool.prototype._add = function add(tx, map, callback, force) {
return next(err); return next(err);
if (!orphans) { if (!orphans) {
if (address && !uniq[address]) {
uniq[address] = true;
map[address].forEach(function(id) {
batch.put(prefix + 't/a/' + id + '/' + hash, DUMMY);
if (tx.ts === 0)
batch.put(prefix + 'p/a/' + id + '/' + hash, DUMMY);
});
}
if (address) { if (address) {
map[address].forEach(function(id) { map[address].forEach(function(id) {
batch.put( batch.put(
@ -533,15 +534,6 @@ TXPool.prototype._add = function add(tx, map, callback, force) {
if (err) if (err)
return done(err); return done(err);
if (!own)
return done(null, false);
batch.put(prefix + 't/t/' + hash, tx.toExtended());
if (tx.ts === 0)
batch.put(prefix + 'p/t/' + hash, DUMMY);
else
batch.put(prefix + 't/h/' + tx.height + '/' + hash, DUMMY);
batch.write(function(err) { batch.write(function(err) {
if (err) if (err)
return done(err); return done(err);
@ -620,13 +612,27 @@ TXPool.prototype._remove = function remove(tx, map, callback) {
var prefix = this.prefix + '/'; var prefix = this.prefix + '/';
var hash = tx.hash('hex'); var hash = tx.hash('hex');
var batch = this.db.batch(); var batch = this.db.batch();
var uniq = {};
batch.del(prefix + 't/t/' + hash); batch.del(prefix + 't/t/' + hash);
if (tx.ts === 0)
batch.del(prefix + 'p/t/' + hash); if (tx.ts === 0) {
else batch.del(prefix + 't/p/t/' + hash);
batch.del(prefix + 't/h/' + tx.height + '/' + hash); batch.del(prefix + 't/s/s/' + pad32(tx.ps) + '/' + hash);
} else {
batch.del(prefix + 't/h/h/' + pad32(tx.height) + '/' + hash);
batch.del(prefix + 't/s/s/' + pad32(tx.ts) + '/' + hash);
}
map.all.forEach(function(id) {
batch.del(prefix + 't/a/' + id + '/' + hash);
if (tx.ts === 0) {
batch.del(prefix + 't/p/a/' + id + '/' + hash);
batch.del(prefix + 't/s/a/' + id + '/' + pad32(tx.ps) + '/' + hash);
} else {
batch.del(prefix + 't/h/a/' + id + '/' + pad32(tx.height) + '/' + hash);
batch.del(prefix + 't/s/a/' + id + '/' + pad32(tx.ts) + '/' + hash);
}
});
this.fillTX(tx, function(err) { this.fillTX(tx, function(err) {
if (err) if (err)
@ -638,15 +644,6 @@ TXPool.prototype._remove = function remove(tx, map, callback) {
if (input.isCoinbase()) if (input.isCoinbase())
return; return;
if (address && !uniq[address]) {
uniq[address] = true;
map[address].forEach(function(id) {
batch.del(prefix + 't/a/' + id + '/' + hash);
if (tx.ts === 0)
batch.del(prefix + 'p/a/' + id + '/' + hash);
});
}
if (!input.output) if (!input.output)
return; return;
@ -670,15 +667,6 @@ TXPool.prototype._remove = function remove(tx, map, callback) {
tx.outputs.forEach(function(output, i) { tx.outputs.forEach(function(output, i) {
var address = output.getAddress(); var address = output.getAddress();
if (address && !uniq[address]) {
uniq[address] = true;
map[address].forEach(function(id) {
batch.del(prefix + 't/a/' + id + '/' + hash);
if (tx.ts === 0)
batch.del(prefix + 'p/a/' + id + '/' + hash);
});
}
if (address) { if (address) {
map[address].forEach(function(id) { map[address].forEach(function(id) {
batch.del(prefix + 'u/a/' + id + '/' + hash + '/' + i); batch.del(prefix + 'u/a/' + id + '/' + hash + '/' + i);
@ -735,10 +723,13 @@ TXPool.prototype.unconfirm = function unconfirm(hash, callback) {
TXPool.prototype._unconfirm = function unconfirm(tx, map, callback) { TXPool.prototype._unconfirm = function unconfirm(tx, map, callback) {
var self = this; var self = this;
var prefix = this.prefix + '/'; var prefix = this.prefix + '/';
var uniq = {};
var hash = tx.hash('hex'); var hash = tx.hash('hex');
var batch = this.db.batch(); var batch = this.db.batch();
var height = tx.height; var height = tx.height;
var ts = tx.ts;
if (height !== -1)
return callback(null, false);
tx.height = -1; tx.height = -1;
tx.ps = utils.now(); tx.ps = utils.now();
@ -747,35 +738,19 @@ TXPool.prototype._unconfirm = function unconfirm(tx, map, callback) {
tx.block = null; tx.block = null;
batch.put(prefix + 't/t/' + hash, tx.toExtended()); batch.put(prefix + 't/t/' + hash, tx.toExtended());
batch.put(prefix + 'p/t/' + hash, DUMMY); batch.put(prefix + 't/p/t/' + hash, DUMMY);
batch.del(prefix + 't/h/' + height + '/' + hash); batch.del(prefix + 't/h/h/' + pad32(height) + '/' + hash);
batch.del(prefix + 't/s/s/' + pad32(ts) + '/' + hash);
batch.put(prefix + 't/s/s/' + pad32(tx.ps) + '/' + hash, DUMMY);
tx.inputs.forEach(function(input) { map.all.forEach(function(id) {
var address; batch.del(prefix + 't/h/a/' + id + '/' + pad32(height) + '/' + hash);
batch.del(prefix + 't/s/a/' + id + '/' + pad32(ts) + '/' + hash);
if (input.isCoinbase()) batch.put(prefix + 't/s/a/' + id + '/' + pad32(tx.ps) + '/' + hash, DUMMY);
return; batch.put(prefix + 't/p/a/' + id + '/' + hash, DUMMY);
address = input.getAddress();
if (address && !uniq[address]) {
uniq[address] = true;
map[address].forEach(function(id) {
batch.put(prefix + 'p/a/' + id + '/' + hash, DUMMY);
});
}
}); });
utils.forEachSerial(tx.outputs, function(output, next, i) { utils.forEachSerial(tx.outputs, function(output, next, i) {
var address = output.getAddress();
if (address && !uniq[address]) {
uniq[address] = true;
map[address].forEach(function(id) {
batch.put(prefix + 'p/a/' + id + '/' + hash, DUMMY);
});
}
self.getCoin(hash, i, function(err, coin) { self.getCoin(hash, i, function(err, coin) {
if (err) if (err)
return next(err); return next(err);
@ -897,8 +872,8 @@ TXPool.prototype.getPendingHashes = function getPendingHashes(address, callback)
} }
iter = this.db.db.iterator({ iter = this.db.db.iterator({
gte: address ? prefix + 'p/a/' + address : prefix + 'p/t', gte: address ? prefix + 't/p/a/' + address : prefix + 't/p/t',
lte: address ? prefix + 'p/a/' + address + '~' : prefix + 'p/t~', lte: address ? prefix + 't/p/a/' + address + '~' : prefix + 't/p/t~',
keys: true, keys: true,
values: false, values: false,
fillCache: false, fillCache: false,
@ -994,41 +969,27 @@ TXPool.prototype.getCoinIDs = function getCoinIDs(address, callback) {
})(); })();
}; };
TXPool.prototype.getHeightHashes = function getHeightHashes(height, callback) { TXPool.prototype.getHeightRangeHashes = function getHeightRangeHashes(address, options, callback) {
var prefix = this.prefix + '/'; var prefix = this.prefix + '/';
var self = this; var self = this;
var txs = []; var txs = [];
var total = 0;
var iter; var iter;
callback = utils.ensure(callback); callback = utils.ensure(callback);
if (Array.isArray(height)) {
return utils.forEachSerial(height, function(height, next) {
self.getHeightHashes(height, function(err, tx) {
if (err)
return next(err);
txs = txs.concat(tx);
next();
});
}, function(err) {
if (err)
return callback(err);
txs = utils.uniqs(txs);
return callback(null, txs);
});
}
iter = this.db.db.iterator({ iter = this.db.db.iterator({
gte: prefix + 't/h/' + height, gte: address
lte: prefix + 't/h/' + height + '~', ? prefix + 't/h/a/' + address + '/' + pad32(options.start) + '/'
: prefix + 't/h/h/' + pad32(options.start) + '/',
lte: address
? prefix + 't/h/a/' + address + '/' + pad32(options.end) + '/~'
: prefix + 't/h/h/' + pad32(options.end) + '/~',
keys: true, keys: true,
values: false, values: false,
fillCache: false, fillCache: false,
keyAsBuffer: false keyAsBuffer: false,
reverse: options.reverse
}); });
(function next() { (function next() {
@ -1049,11 +1010,105 @@ TXPool.prototype.getHeightHashes = function getHeightHashes(height, callback) {
txs.push(key.split('/')[4]); txs.push(key.split('/')[4]);
if (++total === options.limit)
return callback(null, txs);
next(); next();
}); });
})(); })();
}; };
TXPool.prototype.getHeightHashes = function getHeightHashes(height, callback) {
return this.getHeightRangeHashes({ start: height, end: height }, callback);
};
TXPool.prototype.getTimeRangeHashes = function getTimeRangeHashes(address, options, callback) {
var prefix = this.prefix + '/';
var self = this;
var txs = [];
var total = 0;
var iter;
callback = utils.ensure(callback);
iter = this.db.db.iterator({
gte: address
? prefix + 't/s/a/' + address + '/' + pad32(options.start) + '/'
: prefix + 't/s/s/' + pad32(options.start) + '/',
lte: address
? prefix + 't/s/a/' + address + '/' + pad32(options.end) + '/~'
: prefix + 't/s/s/' + pad32(options.end) + '/~',
keys: true,
values: false,
fillCache: false,
keyAsBuffer: false,
reverse: options.reverse
});
(function next() {
iter.next(function(err, key, value) {
if (err) {
return iter.end(function() {
callback(err);
});
}
if (key === undefined) {
return iter.end(function(err) {
if (err)
return callback(err);
return callback(null, txs);
});
}
txs.push(key.split('/')[4]);
if (++total === options.limit)
return callback(null, txs);
next();
});
})();
};
TXPool.prototype.getTimeRange = function getLast(address, options, callback) {
var self = this;
var txs = [];
return this.getTimeRangeHashes(address, options, function(err, hashes) {
if (err)
return callback(err);
utils.forEachSerial(hashes, function(hash, next) {
self.getTX(hash, function(err, tx) {
if (err)
return callback(err);
if (!tx)
return next();
txs.push(tx);
next();
});
}, function(err) {
if (err)
return callback(err);
return callback(null, txs);
});
});
};
TXPool.prototype.getLast = function getLast(address, limit, callback) {
return this.getTimeRange(address, {
start: 0,
end: 0xffffffff,
reverse: true,
limit: limit
}, callback);
};
TXPool.prototype.getAllByAddress = function getAllByAddress(address, callback) { TXPool.prototype.getAllByAddress = function getAllByAddress(address, callback) {
var self = this; var self = this;
var txs = []; var txs = [];
@ -1083,7 +1138,7 @@ TXPool.prototype.getAllByAddress = function getAllByAddress(address, callback) {
}); });
}; };
TXPool.prototype.getLast = function getLast(address, callback) { TXPool.prototype.getLastTime = function getLastTime(address, callback) {
return this.getAllByAddress(address, function(err, txs) { return this.getAllByAddress(address, function(err, txs) {
var lastTs, lastHeight; var lastTs, lastHeight;

View File

@ -152,12 +152,14 @@ Wallet.prototype._init = function _init() {
}); });
this.provider.on('unconfirmed', function(tx) { this.provider.on('unconfirmed', function(tx) {
self.syncOutputDepth(tx);
self.emit('unconfirmed', tx); self.emit('unconfirmed', tx);
}); });
}; };
Wallet.prototype.destroy = function destroy() { Wallet.prototype.destroy = function destroy() {
if (!this.provider)
return;
this.provider.destroy(); this.provider.destroy();
this.provider = null; this.provider = null;
}; };
@ -216,9 +218,6 @@ Wallet.prototype.removeKey = function removeKey(key) {
else if (bcoin.hd.publicKey.isExtended(key)) else if (bcoin.hd.publicKey.isExtended(key))
key = bcoin.hd.publicKey(key); key = bcoin.hd.publicKey(key);
if (key instanceof bcoin.keypair)
key = key.hd;
if (key instanceof bcoin.hd.privateKey) if (key instanceof bcoin.hd.privateKey)
key = key.hdPublicKey; key = key.hdPublicKey;
@ -428,7 +427,7 @@ Wallet.prototype.parsePath = function parsePath(path) {
Wallet.prototype.setReceiveDepth = function setReceiveDepth(depth) { Wallet.prototype.setReceiveDepth = function setReceiveDepth(depth) {
var i; var i;
if (depth <= this.receiveDepth) if (!(depth > this.receiveDepth))
return false; return false;
for (i = this.receiveDepth; i < depth; i++) for (i = this.receiveDepth; i < depth; i++)
@ -445,7 +444,7 @@ Wallet.prototype.setReceiveDepth = function setReceiveDepth(depth) {
Wallet.prototype.setChangeDepth = function setChangeDepth(depth) { Wallet.prototype.setChangeDepth = function setChangeDepth(depth) {
var i; var i;
if (depth <= this.changeDepth) if (!(depth > this.changeDepth))
return false; return false;
for (i = this.changeDepth; i < depth; i++) for (i = this.changeDepth; i < depth; i++)
@ -674,33 +673,30 @@ Wallet.prototype.syncOutputDepth = function syncOutputDepth(tx) {
var depth = this.getOutputDepth(tx); var depth = this.getOutputDepth(tx);
var res = false; var res = false;
if (depth.changeDepth >= this.changeDepth) { if (this.setChangeDepth(depth.changeDepth + 1))
this.setChangeDepth(depth.changeDepth + 1);
res = true; res = true;
}
if (depth.receiveDepth >= this.receiveDepth) { if (this.setReceiveDepth(depth.receiveDepth + 1))
this.setReceiveDepth(depth.receiveDepth + 1);
res = true; res = true;
}
return res; return res;
}; };
Wallet.prototype.scan = function scan(txByAddress, callback) { Wallet.prototype.scan = function scan(txByAddress, callback) {
var self = this; var self = this;
var res = false;
return this._scan({}, txByAddress, function(err, depth) { return this._scan({}, txByAddress, function(err, depth) {
if (err) if (err)
return callback(err); return callback(err);
if (depth.changeDepth >= self.changeDepth) if (self.setChangeDepth(depth.changeDepth + 1))
self.setChangeDepth(depth.changeDepth + 1); res = true;
if (depth.receiveDepth >= self.receiveDepth) if (self.setReceiveDepth(depth.receiveDepth + 1))
self.setReceiveDepth(depth.receiveDepth + 1); res = true;
return callback(null, self); return callback(null, res);
}); });
}; };
@ -822,11 +818,25 @@ Wallet.prototype.getBalance = function getBalance(callback) {
return this.provider.getBalance(callback); return this.provider.getBalance(callback);
}; };
Wallet.prototype.getLast = function getLast(callback) { Wallet.prototype.getLastTime = function getLastTime(callback) {
if (!this.provider) if (!this.provider)
return callback(new Error('No wallet provider available.')); return callback(new Error('No wallet provider available.'));
return this.provider.getLast(callback); return this.provider.getLastTime(callback);
};
Wallet.prototype.getLast = function getLast(limit, callback) {
if (!this.provider)
return callback(new Error('No wallet provider available.'));
return this.provider.getLast(limit, callback);
};
Wallet.prototype.getTimeRange = function getTimeRange(options, callback) {
if (!this.provider)
return callback(new Error('No wallet provider available.'));
return this.provider.getTimeRange(options, callback);
}; };
Wallet.prototype.getPrivateKey = function getPrivateKey(enc) { Wallet.prototype.getPrivateKey = function getPrivateKey(enc) {
@ -957,9 +967,6 @@ Wallet.prototype.toJSON = function toJSON() {
receiveDepth: this.receiveDepth, receiveDepth: this.receiveDepth,
changeDepth: this.changeDepth, changeDepth: this.changeDepth,
master: this.master.toJSON(this.options.passphrase), master: this.master.toJSON(this.options.passphrase),
// Store account key to:
// a. save ourselves derivations.
// b. allow deriving of addresses without decrypting the master key.
accountKey: this.accountKey.xpubkey, accountKey: this.accountKey.xpubkey,
addressMap: this.addressMap, addressMap: this.addressMap,
keys: this.keys.map(function(key) { keys: this.keys.map(function(key) {
@ -993,10 +1000,15 @@ Wallet._fromJSON = function _fromJSON(json, passphrase) {
}; };
}; };
Wallet.fromJSON = function fromJSON(json, passphrase) {
return new Wallet(Wallet._fromJSON(json, passphrase));
};
// For updating the address table quickly // For updating the address table quickly
// without decrypting the master key. // without decrypting the master key.
Wallet.sync = function sync(json, options) { Wallet._syncDepth = function sync(json, options) {
var master, wallet; var master, wallet;
var res = false;
assert.equal(json.v, 3); assert.equal(json.v, 3);
assert.equal(json.name, 'wallet'); assert.equal(json.name, 'wallet');
@ -1008,17 +1020,26 @@ Wallet.sync = function sync(json, options) {
json.master = json.accountKey; json.master = json.accountKey;
wallet = new Wallet(json); wallet = new Wallet(json);
if (options.txs != null) { if (!wallet._initialized)
options.txs.forEach(function(tx) { return;
wallet.syncOutputDepth(tx);
}); if (options.tx != null) {
if (wallet.syncOutputDepth(options.tx))
res = true;
} }
if (options.receiveDepth != null) if (options.receiveDepth != null) {
wallet.setReceiveDepth(options.receiveDepth); if (wallet.setReceiveDepth(options.receiveDepth))
res = true;
}
if (options.changeDepth != null) if (options.changeDepth != null) {
wallet.setChangeDepth(options.changeDepth); if (wallet.setChangeDepth(options.changeDepth))
res = true;
}
if (!res)
return;
wallet = wallet.toJSON(); wallet = wallet.toJSON();
wallet.master = master; wallet.master = master;
@ -1026,8 +1047,27 @@ Wallet.sync = function sync(json, options) {
return wallet; return wallet;
}; };
Wallet.fromJSON = function fromJSON(json, passphrase) { Wallet.syncOutputDepth = function syncOutputDepth(json, tx) {
return new Wallet(Wallet._fromJSON(json, passphrase)); return Wallet._syncDepth(json, { tx: tx });
};
Wallet.setReceiveDepth = function setReceiveDepth(json, receiveDepth) {
return Wallet._syncDepth(json, {
receiveDepth: receiveDepth || 0
});
};
Wallet.setChangeDepth = function setChangeDepth(json, changeDepth) {
return Wallet._syncDepth(json, {
changeDepth: changeDepth || 0
});
};
Wallet.setDepth = function setDepth(json, receiveDepth, changeDepth) {
return Wallet._syncDepth(json, {
receiveDepth: receiveDepth || 0,
changeDepth: changeDepth || 0
});
}; };
/** /**

View File

@ -166,22 +166,7 @@ WalletDB.prototype._init = function _init() {
return; return;
utils.forEachSerial(map.output, function(id, next) { utils.forEachSerial(map.output, function(id, next) {
self.getJSON(id, function(err, json) { self.syncOutputDepth(id, tx, next);
if (err) {
self.emit('error', err);
return next();
}
// Allocate new addresses if necessary.
json = bcoin.wallet.sync(json, { txs: [tx] });
self.saveJSON(id, json, function(err) {
if (err)
return next(err);
next();
});
});
}, function(err) { }, function(err) {
if (err) if (err)
self.emit('error', err); self.emit('error', err);
@ -189,29 +174,53 @@ WalletDB.prototype._init = function _init() {
}); });
}; };
WalletDB.prototype.syncDepth = function syncDepth(id, changeDepth, receiveDepth, callback) { WalletDB.prototype.syncOutputDepth = function syncOutputDepth(id, tx, callback) {
var self = this;
callback = utils.ensure(callback); callback = utils.ensure(callback);
if (!receiveDepth) this.getJSON(id, function(err, json) {
receiveDepth = 0;
if (!changeDepth)
changeDepth = 0;
self.getJSON(id, function(err, json) {
if (err) if (err)
return callback(err); return callback(err);
// Allocate new addresses if necessary. // Allocate new addresses if necessary.
json = bcoin.wallet.sync(json, { json = bcoin.wallet.syncOutputDepth(json, tx);
receiveDepth: receiveDepth,
changeDepth: changeDepth if (!json)
}); return callback();
self.saveJSON(id, json, function(err) { self.saveJSON(id, json, function(err) {
if (err) if (err)
return callback(err); return callback(err);
self.emit('sync depth', id, receiveDepth, changeDepth);
self.emit('sync output depth', id, tx);
callback();
});
});
};
WalletDB.prototype.setDepth = function setDepth(id, receive, change, callback) {
var self = this;
callback = utils.ensure(callback);
this.getJSON(id, function(err, json) {
if (err)
return callback(err);
// Allocate new addresses if necessary.
json = bcoin.wallet.setDepth(json, receive, change);
if (!json)
return callback();
self.saveJSON(id, json, function(err) {
if (err)
return callback(err);
self.emit('set depth', id, receive, change);
callback(); callback();
}); });
}); });
@ -477,6 +486,7 @@ WalletDB.prototype.update = function update(wallet, address) {
if (err) if (err)
self.emit('error', err); self.emit('error', err);
// XXX might have to encrypt key - slow
self._saveDB(wallet.id, wallet.toJSON(), function(err) { self._saveDB(wallet.id, wallet.toJSON(), function(err) {
if (err) if (err)
self.emit('error', err); self.emit('error', err);
@ -520,10 +530,22 @@ WalletDB.prototype.getBalance = function getBalance(id, callback) {
return this.tx.getBalanceByAddress(id, callback); return this.tx.getBalanceByAddress(id, callback);
}; };
WalletDB.prototype.getLast = function getLast(id, callback) { WalletDB.prototype.getLastTime = function getLastTime(id, callback) {
var self = this; var self = this;
id = id.id || id; id = id.id || id;
return this.tx.getLast(id, callback); return this.tx.getLastTime(id, callback);
};
WalletDB.prototype.getLast = function getLast(id, limit, callback) {
var self = this;
id = id.id || id;
return this.tx.getLast(id, limit, callback);
};
WalletDB.prototype.getTimeRange = function getTimeRange(id, options, callback) {
var self = this;
id = id.id || id;
return this.tx.getTimeRange(id, options, callback);
}; };
WalletDB.prototype.fillTX = function fillTX(tx, callback) { WalletDB.prototype.fillTX = function fillTX(tx, callback) {
@ -650,8 +672,16 @@ Provider.prototype.getBalance = function getBalance(callback) {
return this.db.getBalance(this.id, callback); return this.db.getBalance(this.id, callback);
}; };
Provider.prototype.getLast = function getLast(callback) { Provider.prototype.getLastTime = function getLastTime(callback) {
return this.db.getLast(this.id, callback); return this.db.getLastTime(this.id, callback);
};
Provider.prototype.getLast = function getLast(limit, callback) {
return this.db.getLast(this.id, limit, callback);
};
Provider.prototype.getTimeRange = function getTimeRange(options, callback) {
return this.db.getTimeRange(this.id, options, callback);
}; };
Provider.prototype.getTX = function getTX(hash, callback) { Provider.prototype.getTX = function getTX(hash, callback) {