improve tx-pool.

This commit is contained in:
Christopher Jeffrey 2016-02-06 19:10:40 -08:00
parent 7bb20437bc
commit 1761266ba9
4 changed files with 144 additions and 76 deletions

View File

@ -1053,15 +1053,12 @@ Pool.prototype.isWatched = function(tx, bloom) {
return false;
};
Pool.prototype.addWallet = function addWallet(w, defaultTs) {
Pool.prototype.addWallet = function addWallet(w) {
var self = this;
var e;
if (this.loading)
return this.once('load', this.addWallet.bind(this, w, defaultTs));
if (w.loading)
return w.once('load', this.addWallet.bind(this, w, defaultTs));
return this.once('load', this.addWallet.bind(this, w));
if (this.wallets.indexOf(w) !== -1)
return false;
@ -1071,7 +1068,7 @@ Pool.prototype.addWallet = function addWallet(w, defaultTs) {
e = new EventEmitter();
function search(ts) {
function search() {
// Relay pending TXs
// NOTE: It is important to do it after search, because search could
// add TS to pending TXs, thus making them confirmed
@ -1082,17 +1079,21 @@ Pool.prototype.addWallet = function addWallet(w, defaultTs) {
if (self.options.fullNode)
return;
// Search for last week by default
if (!ts)
ts = defaultTs || (utils.now() - 7 * 24 * 3600);
if (self._pendingSearch)
return;
self.searchWallet(ts);
self._pendingSearch = true;
utils.nextTick(function() {
self._pendingSearch = false;
self.searchWallet();
});
}
if (w.loaded)
search(w.lastTs);
if (w.loading)
w.once('load', search);
else
w.once('load', function() { search(w.lastTs) });
search();
return e;
};
@ -1160,16 +1161,21 @@ Pool.prototype.unwatchWallet = function unwatchWallet(w) {
delete w._poolOnRemove;
};
Pool.prototype.searchWallet = function(w) {
Pool.prototype.searchWallet = function(w, h) {
var self = this;
var ts;
var ts, height;
assert(!this.loading);
if (this.options.fullNode)
return;
if (!w) {
if (w == null) {
height = this.wallets.reduce(function(ts, w) {
if (w.lastHeight < height)
return w.lastHeight;
return ts;
}, Infinity);
ts = this.wallets.reduce(function(ts, w) {
if (w.lastTs < ts)
return w.lastTs;
@ -1178,18 +1184,40 @@ Pool.prototype.searchWallet = function(w) {
assert(ts !== Infinity);
} else if (typeof w === 'number') {
ts = w;
height = h;
} else {
if (!w.loaded) {
if (w.loading) {
w.once('load', function() {
self.searchWallet(w);
});
return;
}
ts = w.lastTs;
if (!ts)
ts = utils.now() - 7 * 24 * 3600;
height = w.lastHeight;
}
// Always prefer height
if (height > 0) {
// Back one week
if (!height || height === -1)
height = this.chain.height() - (7 * 24 * 6);
utils.nextTick(function() {
utils.debug('Wallet height: %s', height);
utils.debug(
'Reverted chain to height=%d',
self.chain.height()
);
});
this.chain.resetHeight(height);
return;
}
if (!ts)
ts = utils.now() - 7 * 24 * 3600;
utils.nextTick(function() {
utils.debug('Wallet time: %s', new Date(ts * 1000));
utils.debug(

View File

@ -30,6 +30,7 @@ function TXPool(wallet) {
this._unspent = {};
this._orphans = {};
this._lastTs = 0;
this._lastHeight = 0;
this._loaded = false;
this._addresses = {};
this._sent = new bn(0);
@ -76,22 +77,20 @@ TXPool.prototype._init = function init() {
s.on('end', function() {
self._loaded = true;
self.emit('load', self._lastTs);
self.emit('load', self._lastTs, self._lastHeight);
});
};
TXPool.prototype.add = function add(tx, noWrite, strict) {
var hash = tx.hash('hex');
var updated;
var i, input, key, unspent, index, orphan;
var out, key, orphans, some;
var updated = false;
var i, input, output, unspent, index, orphan;
var key, orphans, some;
this._wallet.fillPrevout(tx);
if (strict) {
if (!this._wallet.ownInput(tx) && !this._wallet.ownOutput(tx))
return false;
}
if (!this._wallet.ownInput(tx) && !this._wallet.ownOutput(tx))
return false;
// Ignore stale pending transactions
if (tx.ts === 0 && tx.ps + 2 * 24 * 3600 < utils.now()) {
@ -107,14 +106,15 @@ TXPool.prototype.add = function add(tx, noWrite, strict) {
this._all[hash].ts = tx.ts;
this._all[hash].block = tx.block;
this._storeTX(hash, tx, noWrite);
this.emit('tx', tx);
this._lastTs = Math.max(tx.ts, this._lastTs);
this._lastHeight = Math.max(tx.getHeight(), this._lastHeight);
this.emit('update', this._lastTs, this._lastHeight, tx);
this.emit('confirmed', tx);
}
return false;
}
this._all[hash] = tx;
updated = false;
this._all[hash] = tx;
// Consume unspent money or add orphans
for (i = 0; i < tx.inputs.length; i++) {
@ -143,15 +143,8 @@ TXPool.prototype.add = function add(tx, noWrite, strict) {
}
// Only add orphans if this input is ours.
// If there is no previous output, there's no way to truly
// verify this is ours, so we assume it is. If we add the
// signature checking code to ownInput for p2sh and p2pk,
// we could in theory use ownInput here (and down below)
// instead.
if (input.output) {
if (!this._wallet.ownOutput(input.output))
continue;
}
if (!this._wallet.ownInput(input))
continue;
// Add orphan, if no parent transaction is yet known
orphan = { tx: tx, index: input.prevout.index };
@ -161,16 +154,6 @@ TXPool.prototype.add = function add(tx, noWrite, strict) {
this._orphans[key] = [orphan];
}
if (!this._wallet.ownOutput(tx)) {
if (updated)
this.emit('update', this._lastTs, tx);
// Save spending TXs without adding unspents
// if (this._wallet.ownInput(tx))
this._storeTX(hash, tx, noWrite);
return;
}
function checkOrphan(orphan) {
var index = orphan.tx._inputIndex(tx.hash('hex'), orphan.index);
assert(index !== -1);
@ -184,13 +167,14 @@ TXPool.prototype.add = function add(tx, noWrite, strict) {
this._removeTX(orphan.tx, noWrite);
return false;
}
this._addInput(orphan.tx, index);
return true;
}
// Add unspent outputs or fullfill orphans
for (i = 0; i < tx.outputs.length; i++) {
out = tx.outputs[i];
output = tx.outputs[i];
// Do not add unspents for outputs that aren't ours.
if (!this._wallet.ownOutput(tx, i))
@ -216,16 +200,41 @@ TXPool.prototype.add = function add(tx, noWrite, strict) {
}
this._lastTs = Math.max(tx.ts, this._lastTs);
this._lastHeight = Math.max(tx.getHeight(), this._lastHeight);
if (updated)
this.emit('update', this._lastTs, tx);
this._storeTX(hash, tx, noWrite);
this.emit('update', this._lastTs, this._lastHeight, tx);
this.emit('tx', tx);
if (tx.ts !== 0)
this.emit('confirmed', tx);
this._storeTX(hash, tx, noWrite);
return true;
};
TXPool.prototype.getTX = function getTX(hash) {
return this._all[hash];
};
TXPool.prototype.getUnspent = function getUnspent(hash, index) {
return this._unspent[hash + '/' + index];
};
TXPool.prototype.addUnspent = function addUnspent(coin) {
var id = coin.hash + '/' + coin.index;
if (!this._unspent[id]) {
this._unspent[id] = coin;
this._addOutput(coin);
this._lastHeight = Math.max(coin.height, this._lastHeight);
this.emit('update', this._lastTs, this._lastHeight);
// Weird workaround to get addresses to update
if (coin.height !== -1)
this.emit('confirmed', coin);
}
};
TXPool.prototype._storeTX = function _storeTX(hash, tx, noWrite) {
var self = this;
@ -285,11 +294,17 @@ TXPool.prototype.getAll = function getAll(address) {
};
TXPool.prototype._addOutput = function _addOutput(tx, i, remove) {
var output = tx.outputs[i];
var address;
if ((tx instanceof bcoin.output) || (tx instanceof bcoin.coin)) {
var output = tx;
if (!this._wallet.ownOutput(output))
return;
} else {
var output = tx.outputs[i];
var address;
if (!this._wallet.ownOutput(tx, i))
return;
if (!this._wallet.ownOutput(tx, i))
return;
}
address = output.getAddress();

View File

@ -1435,13 +1435,16 @@ TX.prototype.hasPrevout = function hasPrevout() {
});
};
TX.prototype.fillPrevout = function fillPrevout(txs) {
TX.prototype.fillPrevout = function fillPrevout(txs, unspent) {
var inputs;
if (txs instanceof bcoin.txPool)
if (txs instanceof bcoin.txPool) {
unspent = txs._unspent;
txs = txs._all;
else if (txs instanceof bcoin.wallet)
} else if (txs instanceof bcoin.wallet) {
unspent = txs.tx._unspent;
txs = txs.tx._all;
}
if (Array.isArray(txs)) {
txs = txs.reduce(function(out, tx) {
@ -1450,9 +1453,24 @@ TX.prototype.fillPrevout = function fillPrevout(txs) {
}, {});
}
if (Array.isArray(unspent)) {
unspent = unspent.reduce(function(out, coin) {
out[coin.hash + '/' + coin.index] = coin;
return out;
}, {});
}
inputs = this.inputs.filter(function(input) {
if (!input.output && txs[input.prevout.hash])
input.output = bcoin.coin(txs[input.prevout.hash], input.prevout.index);
var key;
if (!input.output) {
key = input.prevout.hash + '/' + input.prevout.index;
if (unspent && unspent[key])
input.output = unspent[key];
else if (txs && txs[input.prevout.hash])
input.output = bcoin.coin(txs[input.prevout.hash], input.prevout.index);
}
return !!input.output;
}, this);

View File

@ -151,6 +151,7 @@ function Wallet(options) {
this.storage = options.storage;
this.loading = true;
this.lastTs = 0;
this.lastHeight = 0;
// This is a chicken and egg problem for BIP45. Real address keys cannot be
// generated until all shared keys have been added to the wallet. The flow of
@ -476,36 +477,42 @@ Wallet.prototype._init = function init() {
if (this.tx._loaded) {
this.loading = false;
this._pruneAddresses();
// this._pruneAddresses();
return;
}
// Notify owners about new accepted transactions
this.tx.on('update', function(lastTs, tx) {
this.tx.on('update', function(lastTs, lastHeight, tx) {
var b = this.getBalance();
if (prevBalance && prevBalance.cmp(b) !== 0)
self.emit('balance', b);
self.emit('update', tx);
if (tx)
self.emit('update', tx);
self.lastTs = Math.max(lastTs, self.lastTs);
self.lastHeight = Math.max(lastHeight, self.lastHeight);
prevBalance = b;
});
this.tx.on('tx', function(tx) {
// TX using this address was confirmed.
// Allocate a new address.
if (tx.block) {
if (self.currentAddress.ownOutput(tx))
self.currentAddress = self.createAddress();
if (self.changeAddress.ownOutput(tx))
self.changeAddress = self.createAddress(true);
self._pruneAddresses();
}
self.emit('tx', tx);
});
this.tx.once('load', function(ts) {
this.tx.on('confirmed', function(tx) {
// TX using this address was confirmed.
// Allocate a new address.
if (self.currentAddress.ownOutput(tx))
self.currentAddress = self.createAddress();
if (self.changeAddress.ownOutput(tx))
self.changeAddress = self.createAddress(true);
// self._pruneAddresses();
self.emit('confirmed', tx);
});
this.tx.once('load', function(ts, height) {
self.loading = false;
self.lastTs = ts;
self._pruneAddresses();
self.lastHeight = height;
// self._pruneAddresses();
self.emit('load', ts);
});