mempool. txdb.

This commit is contained in:
Christopher Jeffrey 2016-03-25 20:02:23 -07:00
parent a52d62a71e
commit e205c70f97
12 changed files with 201 additions and 68 deletions

View File

@ -291,6 +291,8 @@ Block.prototype.inspect = function inspect() {
type: this.type,
height: this.height,
hash: utils.revHex(this.hash('hex')),
size: this.getSize(),
virtualSize: this.getVirtualSize(),
date: new Date(this.ts * 1000).toISOString(),
version: this.version,
prevBlock: utils.revHex(this.prevBlock),

View File

@ -164,6 +164,9 @@ Chain.prototype._init = function _init() {
self.loaded = true;
self.emit('open');
if (self.isFull())
self.emit('full');
});
});
});
@ -1203,7 +1206,11 @@ Chain.prototype.add = function add(initial, peer, callback, force) {
bcoin.profiler.snapshot();
utils.nextTick(function() {
if (self.isFull())
self.emit('full');
unlock();
if (err)
callback(err);
else

View File

@ -38,13 +38,18 @@ Fullnode.prototype._init = function _init() {
this.chain = new bcoin.chain(this, {
preload: false,
fsync: false,
spv: false,
prune: this.options.prune,
useCheckpoints: this.options.useCheckpoints
});
// Mempool needs access to blockdb.
this.mempool = new bcoin.mempool(this, {
rbf: false
limitFree: this.options.limitFree,
limitFreeRelay: this.options.limitFreeRelay,
requireStandard: this.options.requireStandard,
rejectInsaneFees: this.options.rejectInsaneFees,
replaceByFee: this.options.replaceByFee
});
// Pool needs access to the chain.
@ -92,12 +97,12 @@ Fullnode.prototype._init = function _init() {
self.emit('error', err);
});
// this.on('tx', function(tx) {
// self.walletdb.addTX(tx, function(err) {
// if (err)
// self.emit('error', err);
// });
// });
this.on('tx', function(tx) {
self.walletdb.addTX(tx, function(err) {
if (err)
self.emit('error', err);
});
});
// Emit events for valid blocks and TXs.
this.chain.on('block', function(block) {
@ -112,14 +117,14 @@ Fullnode.prototype._init = function _init() {
});
// Update the mempool.
// this.chain.on('add block', function(block) {
// self.mempool.addBlock(block);
// });
this.chain.on('add block', function(block) {
self.mempool.addBlock(block);
});
// this.chain.on('remove block', function(block) {
// self.mempool.removeBlock(block);
// self.walletdb.removeBlock(block);
// });
this.chain.on('remove block', function(block) {
self.mempool.removeBlock(block);
self.walletdb.removeBlock(block);
});
function load(err) {
if (err)
@ -335,25 +340,33 @@ Fullnode.prototype.getTXByAddress = function getTXByAddress(addresses, callback)
Fullnode.prototype.fillCoin = function fillCoin(tx, callback) {
var self = this;
this.mempool.fillCoin(tx, function(err, filled) {
this.mempool.tx.isDoubleSpend(tx, function(err, result) {
if (err)
return callback(err);
if (filled)
return callback(null, tx);
if (result)
return callback(null, tx, true);
self.chain.db.fillCoin(tx, callback);
self.mempool.fillCoin(tx, function(err) {
if (err)
return callback(err);
if (tx.hasPrevout())
return callback(null, tx);
self.chain.db.fillCoin(tx, callback);
});
});
};
Fullnode.prototype.fillTX = function fillTX(tx, callback) {
var self = this;
this.mempool.fillTX(tx, function(err, filled) {
this.mempool.fillTX(tx, function(err) {
if (err)
return callback(err);
if (filled)
if (tx.hasPrevout())
return callback(null, tx);
self.chain.db.fillTX(tx, callback);

View File

@ -85,8 +85,13 @@ NodeServer.prototype._init = function _init() {
if (params.receiveDepth)
options.receiveDepth = params.receiveDepth >>> 0;
if (params.address)
if (params.address) {
params.addresses = params.address;
options.address = params.address;
}
if (params.value)
options.value = utils.satoshi(params.value);
if (params.addresses) {
if (typeof params.addresses === 'string')
@ -95,6 +100,9 @@ NodeServer.prototype._init = function _init() {
options.addresses = params.addresses;
}
if (params.passphrase)
options.passphrase = params.passphrase;
if (params.tx) {
try {
options.tx = bcoin.tx.fromRaw(params.tx, 'hex');
@ -241,6 +249,35 @@ NodeServer.prototype._init = function _init() {
});
});
this.post('/wallet/:id/send', function(req, res, next, send) {
self.walletdb.get(req.options.id, req.options.passphrase, function(err, wallet) {
if (err)
return next(err);
if (!wallet)
return send(404);
wallet.createTX({
address: req.options.address,
value: req.options.value
}, function(err, tx) {
wallet.destroy();
if (err)
return next(err);
utils.print(tx);
return send(200, tx.toJSON());
self.pool.sendTX(tx, function(err) {
if (err)
return next(err);
send(200, tx.toJSON());
});
});
});
});
// Update wallet / sync address depth
this.put('/wallet/:id', function(req, res, next, send) {
var id = req.options.id;
@ -361,9 +398,12 @@ NodeServer.prototype._init = function _init() {
// Broadcast TX
this.post('/broadcast', function(req, res, next, send) {
var tx = req.options.tx;
self.pool.broadcast(tx);
send(200, { success: true });
self.pool.sendTX(tx, function(err) {
if (err)
return callback(err);
send(200, { success: true });
});
});
this.server.on('error', function(err) {

View File

@ -59,9 +59,9 @@ function Mempool(node, options) {
this.freeCount = 0;
this.lastTime = 0;
this.limitFree = this.options.limitFree !== false;
this.limitFreeRelay = this.options.limitFreeRelay || 15;
this.requireStandard = this.options.requireStandard !== false;
this.limitFree = this.options.limitFree !== false;
this.rejectInsaneFees = this.options.rejectInsaneFees !== false;
Mempool.global = this;
@ -208,24 +208,18 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback, force) {
if (exists)
return callback();
self.node.fillCoin(tx, function(err) {
self.node.fillCoin(tx, function(err, tx, doubleSpend) {
if (err)
return callback(err);
if (!tx.hasPrevout()) {
return self.tx.isDoubleSpend(tx, function(err, result) {
if (err)
return callback(err);
if (result) {
peer.sendReject(tx, 'bad-txns-inputs-spent', 0);
return callback(new VerifyError('bad-txns-inputs-spent', 0));
}
return self.storeOrphan(tx, callback);
});
if (doubleSpend) {
peer.sendReject(tx, 'bad-txns-inputs-spent', 0);
return callback(new VerifyError('bad-txns-inputs-spent', 0));
}
if (!tx.hasPrevout())
return self.storeOrphan(tx, callback);
self.verify(tx, function(err) {
if (err) {
if (err.type === 'VerifyError' && err.score >= 0)
@ -247,6 +241,8 @@ Mempool.prototype.addUnchecked = function addUnchecked(tx, peer, callback) {
self.emit('tx', tx);
utils.debug('Added tx %s to the mempool.', tx.rhash);
self.resolveOrphans(tx, function(err, resolved) {
if (err)
return callback(err);

View File

@ -26,7 +26,7 @@ function MTX(options) {
this.options = options;
this.type = 'mtx';
this.type = 'tx';
this.version = options.version || 1;
this.inputs = [];
this.outputs = [];

View File

@ -228,7 +228,7 @@ Peer.prototype.broadcast = function broadcast(items) {
clearInterval(old.interval);
}
var inv = this.framer.inv([{
var inv = this.framer.inv([{
type: item.type,
hash: item.hash()
}]);

View File

@ -213,17 +213,20 @@ Pool.prototype._init = function _init() {
self.resolveOrphan(self.peers.load, null, data.hash);
});
this.chain.on('full', function() {
self._stopTimer();
self._stopInterval();
if (!self.synced)
self.getMempool();
self.synced = true;
self.emit('full');
utils.debug('Chain is fully synced (height=%d).', self.chain.height);
});
(this.options.wallets || []).forEach(function(wallet) {
self.addWallet(wallet);
});
// Chain is full and up-to-date
if (this.chain.isFull()) {
this.synced = true;
this.emit('full');
utils.debug('Chain is fully synced (height=%d).', this.chain.height);
}
this.startServer();
};
@ -341,14 +344,8 @@ Pool.prototype._startTimer = function _startTimer() {
return;
// Chain is full and up-to-date
if (self.chain.isFull()) {
self._stopTimer();
self._stopInterval();
self.synced = true;
self.emit('full');
utils.debug('Chain is fully synced (height=%d).', self.chain.height);
if (self.chain.isFull())
return;
}
if (self.peers.load) {
self.peers.load.destroy();
@ -376,6 +373,10 @@ Pool.prototype._startInterval = function _startInterval() {
if (!self.syncing)
return;
// Chain is full and up-to-date
if (self.chain.isFull())
return;
utils.debug('Stall recovery: loading again.');
// self._load();
}
@ -889,7 +890,7 @@ Pool.prototype._handleTX = function _handleTX(tx, peer, callback) {
return callback(err);
addMempool(tx, peer, function(err) {
if (err && self.synced)
if (err)
utils.debug('Mempool error: %s', err.message);
if (updated || tx.block)
@ -1649,11 +1650,9 @@ Pool.prototype.getTX = function getTX(hash, range, callback) {
};
Pool.prototype.sendTX = function sendTX(tx, callback) {
callback = utils.asyncify(callback);
// Failsafe to avoid getting banned by bitcoind nodes.
if (!bcoin.mempool.checkTX(tx))
return callback(new Error('CheckTransaction failed.'));
return utils.asyncify(callback)(new Error('CheckTransaction failed.'));
return this.broadcast(tx, callback);
};
@ -1677,14 +1676,17 @@ Pool.prototype.broadcast = function broadcast(msg, callback) {
this.inv.list.push(entry);
this.peers.regular.forEach(function(peer) {
this.peers.all.forEach(function(peer) {
var result = peer.broadcast(msg);
if (!result)
return;
result[0].once('request', function() {
e.emit('ack', peer);
callback();
// Give them a chance to send a reject.
setTimeout(function() {
callback();
}, 100);
});
result[0].once('reject', function(payload) {

View File

@ -585,7 +585,7 @@ Framer.reject = function reject(details, writer) {
p.writeU8(constants.reject[details.ccode] || constants.reject.malformed);
p.writeVarString(details.reason || '', 'ascii');
if (details.data)
p.writeBytes(details.data);
p.writeHash(details.data);
if (!writer)
p = p.render();

View File

@ -1083,6 +1083,8 @@ TX.prototype.inspect = function inspect() {
type: this.type,
hash: utils.revHex(this.hash('hex')),
witnessHash: utils.revHex(this.witnessHash('hex')),
size: this.getSize(),
virtualSize: this.getVirtualSize(),
height: this.height,
value: utils.btc(this.getValue()),
fee: utils.btc(this.getFee()),

View File

@ -371,12 +371,14 @@ TXPool.prototype._add = function add(tx, map, callback, force) {
prefix + 's/t/'
+ input.prevout.hash
+ '/' + input.prevout.index,
DUMMY);
tx.hash());
}
return next();
}
input.output = null;
// Only add orphans if this input is ours.
if (self.options.mapAddress) {
if (!address || !map[address].length)
@ -385,16 +387,24 @@ TXPool.prototype._add = function add(tx, map, callback, force) {
self.isSpent(input.prevout.hash, input.prevout.index, function(err, result) {
if (err)
return callback(err);
return next(err);
// Are we double-spending?
if (result)
return callback(new Error('Transaction is double-spending.'));
if (result) {
if (!self.options.indexSpent)
return next(new Error('Transaction is double-spending.'));
return self._removeSpenders(hash, function(err) {
if (err)
return next(err);
batch.clear();
self._add(tx, map, callback, true);
});
}
// Add orphan, if no parent transaction is yet known
self._addOrphan(key, hash, i, function(err, orphans) {
if (err)
return callback(err);
return next(err);
batch.put(prefix + 'o/' + key, orphans);
@ -515,6 +525,64 @@ TXPool.prototype._add = function add(tx, map, callback, force) {
}, true);
};
TXPool.prototype._removeSpenders = function removeSpenders(hash, callback) {
var self = this;
this.getTX(hash, function(err, tx) {
if (err)
return callback(err);
if (!tx)
return callback(new Error('Could not find spender.'));
if (tx.ts !== 0)
return callback(new Error('Transaction is double-spending.'));
utils.forEachSerial(tx.outputs, function(next, output, i) {
self.isSpent(hash, i, function(err, spent) {
if (err)
return next(err);
if (spent)
return self._removeSpenders(spent, next);
next();
});
}, function(err) {
if (err)
return callback(err);
return self.lazyRemove(tx, callback, true);
});
});
};
TXPool.prototype._collectSpenders = function _collectSpenders(tx, callback, spenders) {
var self = this;
var hash = tx.hash('hex');
if (!spenders)
spenders = [];
utils.forEachSerial(tx.outputs, function(next, output, i) {
self.isSpent(hash, i, function(err, spentBy) {
if (err)
return next(err);
if (spentBy) {
spenders.push(spentBy);
return self.getTX(spentBy, function(err, tx) {
if (err)
return next(err);
if (!tx)
return next();
return self._removeSpenders(tx, next, spenders);
});
}
next();
});
}, function(err) {
if (err)
return callback(err);
return callback(null, spenders);
});
};
TXPool.prototype.isDoubleSpend = function isDoubleSpend(tx, callback) {
var self = this;
utils.everySerial(tx.inputs, function(input, next) {
@ -541,7 +609,7 @@ TXPool.prototype.isSpent = function isSpent(hash, index, callback, checkCoin) {
if (err && err.type !== 'NotFoundError')
return callback(err);
return callback(null, !!exists);
return callback(null, exists ? exists.toString('hex') : null);
});
}

View File

@ -98,7 +98,7 @@ WalletDB.prototype._init = function _init() {
});
this.tx = new bcoin.txdb('w', this.db, {
indexSpent: false,
indexSpent: true,
indexExtra: true,
indexAddress: true,
mapAddress: true,
@ -438,6 +438,9 @@ WalletDB.prototype.create = function create(options, callback) {
}
done();
} else {
if (bcoin.protocol.network.type === 'segnet')
options.witness = options.witness !== false;
options.provider = new Provider(self);
wallet = new bcoin.wallet(options);
self.saveJSON(wallet.id, wallet.toJSON(), done);