txdb and mempool work.
This commit is contained in:
parent
e205c70f97
commit
37c488c802
@ -118,12 +118,21 @@ Fullnode.prototype._init = function _init() {
|
|||||||
|
|
||||||
// Update the mempool.
|
// Update the mempool.
|
||||||
this.chain.on('add block', function(block) {
|
this.chain.on('add block', function(block) {
|
||||||
self.mempool.addBlock(block);
|
self.mempool.addBlock(block, function(err) {
|
||||||
|
if (err)
|
||||||
|
self.emit('error', err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.chain.on('remove block', function(block) {
|
this.chain.on('remove block', function(block) {
|
||||||
self.mempool.removeBlock(block);
|
self.mempool.removeBlock(block, function(err) {
|
||||||
self.walletdb.removeBlock(block);
|
if (err)
|
||||||
|
self.emit('error', err);
|
||||||
|
});
|
||||||
|
self.walletdb.removeBlock(block, function(err) {
|
||||||
|
if (err)
|
||||||
|
self.emit('error', err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function load(err) {
|
function load(err) {
|
||||||
@ -149,7 +158,7 @@ Fullnode.prototype._init = function _init() {
|
|||||||
|
|
||||||
self.createWallet(options, function(err, wallet) {
|
self.createWallet(options, function(err, wallet) {
|
||||||
if (err)
|
if (err)
|
||||||
throw err;
|
return self.emit('error', err);
|
||||||
|
|
||||||
// Set the miner payout address if the
|
// Set the miner payout address if the
|
||||||
// programmer didn't pass one in.
|
// programmer didn't pass one in.
|
||||||
@ -340,22 +349,14 @@ Fullnode.prototype.getTXByAddress = function getTXByAddress(addresses, callback)
|
|||||||
Fullnode.prototype.fillCoin = function fillCoin(tx, callback) {
|
Fullnode.prototype.fillCoin = function fillCoin(tx, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this.mempool.tx.isDoubleSpend(tx, function(err, result) {
|
this.mempool.fillCoin(tx, function(err) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
if (result)
|
if (tx.hasPrevout())
|
||||||
return callback(null, tx, true);
|
return callback(null, tx);
|
||||||
|
|
||||||
self.mempool.fillCoin(tx, function(err) {
|
self.chain.db.fillCoin(tx, callback);
|
||||||
if (err)
|
|
||||||
return callback(err);
|
|
||||||
|
|
||||||
if (tx.hasPrevout())
|
|
||||||
return callback(null, tx);
|
|
||||||
|
|
||||||
self.chain.db.fillCoin(tx, callback);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -31,7 +31,6 @@ function Mempool(node, options) {
|
|||||||
this.options = options;
|
this.options = options;
|
||||||
this.node = node;
|
this.node = node;
|
||||||
this.chain = node.chain;
|
this.chain = node.chain;
|
||||||
// this.db = node.chain.db.db;
|
|
||||||
|
|
||||||
this.db = bcoin.ldb('mempool', {
|
this.db = bcoin.ldb('mempool', {
|
||||||
db: 'memdown'
|
db: 'memdown'
|
||||||
@ -100,22 +99,34 @@ Mempool.prototype.open = function open(callback) {
|
|||||||
return this.db.open(callback);
|
return this.db.open(callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
Mempool.prototype.addBlock = function addBlock(block, callback) {
|
Mempool.prototype.addBlock = function addBlock(block, callback, force) {
|
||||||
var self = this;
|
var self = this;
|
||||||
callback = utils.ensure(callback);
|
var unlock = this._lock(addBlock, [block, callback], force);
|
||||||
// Remove now-mined transactions
|
if (!unlock)
|
||||||
// XXX should batch this
|
return;
|
||||||
utils.forEachSerial(block.txs, function(tx, next) {
|
|
||||||
self.tx.remove(tx, next);
|
callback = utils.wrap(callback, unlock);
|
||||||
}, callback);
|
|
||||||
|
this.open(function(err) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
|
||||||
|
utils.forEachSerial(block.txs, function(tx, next) {
|
||||||
|
self.tx.removeUnchecked(tx, next);
|
||||||
|
}, callback);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Mempool.prototype.removeBlock = function removeBlock(block, callback) {
|
Mempool.prototype.removeBlock = function removeBlock(block, callback, force) {
|
||||||
var self = this;
|
var self = this;
|
||||||
callback = utils.ensure(callback);
|
var unlock = this._lock(removeBlock, [block, callback], force);
|
||||||
// XXX should batch this
|
if (!unlock)
|
||||||
|
return;
|
||||||
|
|
||||||
|
callback = utils.wrap(callback, unlock);
|
||||||
|
|
||||||
utils.forEachSerial(block.txs.slice().reverse(), function(tx, next) {
|
utils.forEachSerial(block.txs.slice().reverse(), function(tx, next) {
|
||||||
self.tx.add(tx, next);
|
self.tx.addUnchecked(tx, next);
|
||||||
}, callback);
|
}, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -208,7 +219,7 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback, force) {
|
|||||||
if (exists)
|
if (exists)
|
||||||
return callback();
|
return callback();
|
||||||
|
|
||||||
self.node.fillCoin(tx, function(err, tx, doubleSpend) {
|
self.tx.isDoubleSpend(tx, function(err, doubleSpend) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
@ -217,17 +228,22 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback, force) {
|
|||||||
return callback(new VerifyError('bad-txns-inputs-spent', 0));
|
return callback(new VerifyError('bad-txns-inputs-spent', 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tx.hasPrevout())
|
self.node.fillCoin(tx, function(err) {
|
||||||
return self.storeOrphan(tx, callback);
|
if (err)
|
||||||
|
|
||||||
self.verify(tx, function(err) {
|
|
||||||
if (err) {
|
|
||||||
if (err.type === 'VerifyError' && err.score >= 0)
|
|
||||||
peer.sendReject(tx, err.reason, err.score);
|
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
|
||||||
|
|
||||||
self.addUnchecked(tx, peer, callback);
|
if (!tx.hasPrevout())
|
||||||
|
return self.storeOrphan(tx, callback);
|
||||||
|
|
||||||
|
self.verify(tx, function(err) {
|
||||||
|
if (err) {
|
||||||
|
if (err.type === 'VerifyError' && err.score >= 0)
|
||||||
|
peer.sendReject(tx, err.reason, err.score);
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.addUnchecked(tx, peer, callback);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -235,7 +251,7 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback, force) {
|
|||||||
|
|
||||||
Mempool.prototype.addUnchecked = function addUnchecked(tx, peer, callback) {
|
Mempool.prototype.addUnchecked = function addUnchecked(tx, peer, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
self.tx.add(tx, function(err) {
|
self.tx.addUnchecked(tx, function(err) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
@ -351,7 +367,7 @@ Mempool.prototype._hasTX = function hasTX(tx, callback, force) {
|
|||||||
if (result)
|
if (result)
|
||||||
return callback(null, result);
|
return callback(null, result);
|
||||||
|
|
||||||
self.db.get('D/' + hash, function(err, tx) {
|
self.db.get('m/D/' + hash, function(err, tx) {
|
||||||
if (err && err.type !== 'NotFoundError')
|
if (err && err.type !== 'NotFoundError')
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
@ -378,7 +394,7 @@ Mempool.prototype.storeOrphan = function storeOrphan(tx, callback, force) {
|
|||||||
assert(outputs.length > 0);
|
assert(outputs.length > 0);
|
||||||
|
|
||||||
utils.forEachSerial(outputs, function(key, next) {
|
utils.forEachSerial(outputs, function(key, next) {
|
||||||
self.db.get('d/' + key, function(err, buf) {
|
self.db.get('m/d/' + key, function(err, buf) {
|
||||||
if (err && err.type !== 'NotFoundError')
|
if (err && err.type !== 'NotFoundError')
|
||||||
return next(err);
|
return next(err);
|
||||||
|
|
||||||
@ -389,7 +405,7 @@ Mempool.prototype.storeOrphan = function storeOrphan(tx, callback, force) {
|
|||||||
|
|
||||||
p.writeHash(hash);
|
p.writeHash(hash);
|
||||||
|
|
||||||
batch.put('d/' + key, p.render());
|
batch.put('m/d/' + key, p.render());
|
||||||
|
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
@ -397,7 +413,7 @@ Mempool.prototype.storeOrphan = function storeOrphan(tx, callback, force) {
|
|||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
batch.put('D/' + hash, tx.toExtended(true));
|
batch.put('m/D/' + hash, tx.toExtended(true));
|
||||||
batch.write(callback);
|
batch.write(callback);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -418,7 +434,7 @@ Mempool.prototype.resolveOrphans = function resolveOrphans(tx, callback, force)
|
|||||||
var batch = this.db.batch();
|
var batch = this.db.batch();
|
||||||
var p;
|
var p;
|
||||||
|
|
||||||
this.db.get('d/' + hash, function(err, buf) {
|
this.db.get('m/d/' + hash, function(err, buf) {
|
||||||
if (err && err.type !== 'NotFoundError')
|
if (err && err.type !== 'NotFoundError')
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
@ -439,7 +455,7 @@ Mempool.prototype.resolveOrphans = function resolveOrphans(tx, callback, force)
|
|||||||
p.end();
|
p.end();
|
||||||
|
|
||||||
utils.forEachSerial(hashes, function(orphanHash, next, i) {
|
utils.forEachSerial(hashes, function(orphanHash, next, i) {
|
||||||
self.db.get('D/' + orphanHash, function(err, orphan) {
|
self.db.get('m/D/' + orphanHash, function(err, orphan) {
|
||||||
if (err && err.type !== 'NotFoundError')
|
if (err && err.type !== 'NotFoundError')
|
||||||
return next(err);
|
return next(err);
|
||||||
|
|
||||||
@ -452,10 +468,13 @@ Mempool.prototype.resolveOrphans = function resolveOrphans(tx, callback, force)
|
|||||||
return next(e);
|
return next(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
orphan.fillPrevout(tx);
|
orphan.inputs.forEach(function(input) {
|
||||||
|
if (!input.output && input.prevout.hash === hash)
|
||||||
|
input.output = bcoin.coin(tx, input.prevout.index);
|
||||||
|
});
|
||||||
|
|
||||||
if (orphan.hasPrevout()) {
|
if (orphan.hasPrevout()) {
|
||||||
batch.del('D/' + orphanHash);
|
batch.del('m/D/' + orphanHash);
|
||||||
return self.verify(orphan, function(err) {
|
return self.verify(orphan, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
if (err.type === 'VerifyError')
|
if (err.type === 'VerifyError')
|
||||||
@ -467,7 +486,7 @@ Mempool.prototype.resolveOrphans = function resolveOrphans(tx, callback, force)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
batch.put('D/' + orphanHash, orphan.toExtended(true));
|
batch.put('m/D/' + orphanHash, orphan.toExtended(true));
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
@ -481,7 +500,7 @@ Mempool.prototype.resolveOrphans = function resolveOrphans(tx, callback, force)
|
|||||||
return callback(null, resolved);
|
return callback(null, resolved);
|
||||||
}
|
}
|
||||||
|
|
||||||
batch.del('d/' + hash);
|
batch.del('m/d/' + hash);
|
||||||
|
|
||||||
return batch.write(done);
|
return batch.write(done);
|
||||||
});
|
});
|
||||||
@ -492,40 +511,6 @@ Mempool.prototype.getInv = function getInv(callback) {
|
|||||||
return this.tx.getAllHashes(callback);
|
return this.tx.getAllHashes(callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
Mempool.prototype.remove =
|
|
||||||
Mempool.prototype.removeTX = function removeTX(hash, callback, force) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
var unlock = this._lock(removeTX, [hash, callback], force);
|
|
||||||
if (!unlock)
|
|
||||||
return;
|
|
||||||
|
|
||||||
function getTX() {
|
|
||||||
if (hash instanceof bcoin.tx)
|
|
||||||
return callback(null, hash);
|
|
||||||
|
|
||||||
return self.getTX(hash, function(err, tx) {
|
|
||||||
if (err)
|
|
||||||
return callback(err);
|
|
||||||
if (!tx)
|
|
||||||
return callback();
|
|
||||||
return self.node.fillTX(tx, callback);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return getTX(function(err, tx) {
|
|
||||||
if (err)
|
|
||||||
return callback(err);
|
|
||||||
|
|
||||||
self.tx.remove(tx, function(err) {
|
|
||||||
if (err)
|
|
||||||
return callback(err);
|
|
||||||
|
|
||||||
self.emit('remove tx', tx);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Mempool.prototype.checkTX = function checkTX(tx, peer) {
|
Mempool.prototype.checkTX = function checkTX(tx, peer) {
|
||||||
return Mempool.checkTX(tx, peer);
|
return Mempool.checkTX(tx, peer);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -30,6 +30,7 @@ function TXPool(prefix, db, options) {
|
|||||||
this.options = options;
|
this.options = options;
|
||||||
this.busy = false;
|
this.busy = false;
|
||||||
this.jobs = [];
|
this.jobs = [];
|
||||||
|
this.locker = new bcoin.locker(this);
|
||||||
|
|
||||||
if (this.options.mapAddress)
|
if (this.options.mapAddress)
|
||||||
this.options.indexAddress = true;
|
this.options.indexAddress = true;
|
||||||
@ -38,40 +39,7 @@ function TXPool(prefix, db, options) {
|
|||||||
utils.inherits(TXPool, EventEmitter);
|
utils.inherits(TXPool, EventEmitter);
|
||||||
|
|
||||||
TXPool.prototype._lock = function _lock(func, args, force) {
|
TXPool.prototype._lock = function _lock(func, args, force) {
|
||||||
var self = this;
|
return this.locker.lock(func, args, force);
|
||||||
var called;
|
|
||||||
|
|
||||||
if (force) {
|
|
||||||
assert(this.busy);
|
|
||||||
return function unlock() {
|
|
||||||
assert(!called);
|
|
||||||
called = true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.busy) {
|
|
||||||
this.jobs.push([func, args]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.busy = true;
|
|
||||||
|
|
||||||
return function unlock() {
|
|
||||||
var item;
|
|
||||||
|
|
||||||
assert(!called);
|
|
||||||
called = true;
|
|
||||||
|
|
||||||
self.busy = false;
|
|
||||||
|
|
||||||
if (self.jobs.length === 0) {
|
|
||||||
self.emit('flush');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
item = self.jobs.shift();
|
|
||||||
item[0].apply(self, item[1]);
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
TXPool.prototype.getMap = function getMap(tx, callback) {
|
TXPool.prototype.getMap = function getMap(tx, callback) {
|
||||||
@ -325,20 +293,24 @@ TXPool.prototype._add = function add(tx, map, callback, force) {
|
|||||||
|
|
||||||
// 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, address;
|
||||||
|
|
||||||
if (tx.isCoinbase())
|
if (tx.isCoinbase())
|
||||||
return next();
|
return next();
|
||||||
|
|
||||||
key = input.prevout.hash + '/' + input.prevout.index;
|
address = input.getAddress();
|
||||||
|
|
||||||
|
// Only add orphans if this input is ours.
|
||||||
|
if (self.options.mapAddress) {
|
||||||
|
if (!address || !map[address].length)
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
self.getCoin(input.prevout.hash, input.prevout.index, function(err, coin) {
|
self.getCoin(input.prevout.hash, input.prevout.index, function(err, coin) {
|
||||||
var address;
|
|
||||||
|
|
||||||
if (err)
|
if (err)
|
||||||
return next(err);
|
return next(err);
|
||||||
|
|
||||||
address = input.getAddress();
|
key = input.prevout.hash + '/' + input.prevout.index;
|
||||||
|
|
||||||
if (coin) {
|
if (coin) {
|
||||||
// Add TX to inputs and spend money
|
// Add TX to inputs and spend money
|
||||||
@ -354,50 +326,52 @@ TXPool.prototype._add = function add(tx, map, callback, force) {
|
|||||||
|
|
||||||
if (self.options.indexAddress && address) {
|
if (self.options.indexAddress && address) {
|
||||||
map[address].forEach(function(id) {
|
map[address].forEach(function(id) {
|
||||||
batch.del(
|
batch.del(prefix + 'u/a/' + id + '/' + key);
|
||||||
prefix + 'u/a/' + id
|
|
||||||
+ '/' + input.prevout.hash
|
|
||||||
+ '/' + input.prevout.index);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
batch.del(
|
batch.del(prefix + 'u/t/' + key);
|
||||||
prefix + 'u/t/'
|
batch.put(prefix + 's/t/' + key, tx.hash());
|
||||||
+ input.prevout.hash
|
|
||||||
+ '/' + input.prevout.index);
|
|
||||||
|
|
||||||
if (self.options.indexSpent) {
|
|
||||||
batch.put(
|
|
||||||
prefix + 's/t/'
|
|
||||||
+ input.prevout.hash
|
|
||||||
+ '/' + input.prevout.index,
|
|
||||||
tx.hash());
|
|
||||||
}
|
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
input.output = null;
|
input.output = null;
|
||||||
|
|
||||||
// Only add orphans if this input is ours.
|
self.isSpent(input.prevout.hash, input.prevout.index, function(err, spentBy) {
|
||||||
if (self.options.mapAddress) {
|
|
||||||
if (!address || !map[address].length)
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.isSpent(input.prevout.hash, input.prevout.index, function(err, result) {
|
|
||||||
if (err)
|
if (err)
|
||||||
return next(err);
|
return next(err);
|
||||||
|
|
||||||
// Are we double-spending?
|
// Are we double-spending?
|
||||||
if (result) {
|
// Replace older txs with newer ones.
|
||||||
if (!self.options.indexSpent)
|
if (spentBy) {
|
||||||
return next(new Error('Transaction is double-spending.'));
|
return self.getTX(input.prevout.hash, function(err, prev) {
|
||||||
return self._removeSpenders(hash, function(err) {
|
|
||||||
if (err)
|
if (err)
|
||||||
return next(err);
|
return next(err);
|
||||||
batch.clear();
|
|
||||||
self._add(tx, map, callback, true);
|
if (!prev)
|
||||||
|
return callback(new Error('Could not find double-spent coin.'));
|
||||||
|
|
||||||
|
input.output = bcoin.coin(prev, input.prevout.index);
|
||||||
|
|
||||||
|
// Skip invalid transactions
|
||||||
|
if (self.options.verify) {
|
||||||
|
if (!tx.verify(i))
|
||||||
|
return callback(null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return self._removeSpenders(spentBy, tx, function(err, result) {
|
||||||
|
if (err)
|
||||||
|
return next(err);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
assert(tx.ts === 0, 'I\'m confused');
|
||||||
|
return callback(null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
batch.clear();
|
||||||
|
self._add(tx, map, callback, true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -525,7 +499,7 @@ TXPool.prototype._add = function add(tx, map, callback, force) {
|
|||||||
}, true);
|
}, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
TXPool.prototype._removeSpenders = function removeSpenders(hash, callback) {
|
TXPool.prototype._removeSpenders = function removeSpenders(hash, ref, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.getTX(hash, function(err, tx) {
|
this.getTX(hash, function(err, tx) {
|
||||||
if (err)
|
if (err)
|
||||||
@ -535,54 +509,31 @@ TXPool.prototype._removeSpenders = function removeSpenders(hash, callback) {
|
|||||||
return callback(new Error('Could not find spender.'));
|
return callback(new Error('Could not find spender.'));
|
||||||
|
|
||||||
if (tx.ts !== 0)
|
if (tx.ts !== 0)
|
||||||
return callback(new Error('Transaction is double-spending.'));
|
return callback(null, false);
|
||||||
|
|
||||||
utils.forEachSerial(tx.outputs, function(next, output, i) {
|
if (ref.ts === 0 && ref.ps < ts.ps)
|
||||||
|
return callback(null, false);
|
||||||
|
|
||||||
|
utils.forEachSerial(tx.outputs, function(output, next, i) {
|
||||||
self.isSpent(hash, i, function(err, spent) {
|
self.isSpent(hash, i, function(err, spent) {
|
||||||
if (err)
|
if (err)
|
||||||
return next(err);
|
return next(err);
|
||||||
if (spent)
|
if (spent)
|
||||||
return self._removeSpenders(spent, next);
|
return self._removeSpenders(spent, ref, next);
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
return self.lazyRemove(tx, callback, true);
|
return self.lazyRemove(tx, function(err) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
return callback(null, true);
|
||||||
|
}, 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) {
|
TXPool.prototype.isDoubleSpend = function isDoubleSpend(tx, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
utils.everySerial(tx.inputs, function(input, next) {
|
utils.everySerial(tx.inputs, function(input, next) {
|
||||||
@ -601,36 +552,19 @@ TXPool.prototype.isDoubleSpend = function isDoubleSpend(tx, callback) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
TXPool.prototype.isSpent = function isSpent(hash, index, callback, checkCoin) {
|
TXPool.prototype.isSpent = function isSpent(hash, index, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
var prefix = this.prefix + '/';
|
||||||
|
var key = prefix + 's/t/' + hash + '/' + index;
|
||||||
|
|
||||||
if (this.options.indexSpent) {
|
return this.db.get(key, function(err, hash) {
|
||||||
return this.db.get('s/t/' + hash + '/' + index, function(err, exists) {
|
if (err && err.type !== 'NotFoundError')
|
||||||
if (err && err.type !== 'NotFoundError')
|
|
||||||
return callback(err);
|
|
||||||
|
|
||||||
return callback(null, exists ? exists.toString('hex') : null);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCoin(callback) {
|
|
||||||
if (!checkCoin)
|
|
||||||
return callback(null, null);
|
|
||||||
return self.getCoin(hash, index, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
return getCoin(function(err, coin) {
|
|
||||||
if (err)
|
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
if (coin)
|
if (!hash)
|
||||||
return callback(null, false);
|
return callback(null, null);
|
||||||
|
|
||||||
return self.getTX(hash, function(err, tx) {
|
return callback(null, utils.toHex(hash));
|
||||||
if (err)
|
|
||||||
return callback(err);
|
|
||||||
return callback(null, !!tx);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -728,10 +662,6 @@ TXPool.prototype._confirm = function _confirm(tx, map, callback, force) {
|
|||||||
|
|
||||||
TXPool.prototype.remove = function remove(hash, callback, force) {
|
TXPool.prototype.remove = function remove(hash, callback, force) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var first = Array.isArray(hash) ? hash[0] : hash;
|
|
||||||
|
|
||||||
if (!this.options.indexExtra && (first instanceof bcoin.tx))
|
|
||||||
return this.lazyRemove(hash, callback, force);
|
|
||||||
|
|
||||||
if (Array.isArray(hash)) {
|
if (Array.isArray(hash)) {
|
||||||
return utils.forEachSerial(hash, function(hash, next) {
|
return utils.forEachSerial(hash, function(hash, next) {
|
||||||
@ -858,11 +788,9 @@ TXPool.prototype._remove = function remove(tx, map, callback, force) {
|
|||||||
+ '/' + input.prevout.index,
|
+ '/' + input.prevout.index,
|
||||||
input.output.toRaw());
|
input.output.toRaw());
|
||||||
|
|
||||||
if (self.options.indexSpent) {
|
batch.del(prefix + 's/t/'
|
||||||
batch.del(prefix + 's/t/'
|
+ input.prevout.hash
|
||||||
+ input.prevout.hash
|
+ '/' + input.prevout.index);
|
||||||
+ '/' + input.prevout.index);
|
|
||||||
}
|
|
||||||
|
|
||||||
batch.del(prefix + 'o/' + input.prevout.hash + '/' + input.prevout.index);
|
batch.del(prefix + 'o/' + input.prevout.hash + '/' + input.prevout.index);
|
||||||
});
|
});
|
||||||
@ -1603,6 +1531,158 @@ TXPool.prototype.getBalance = function getBalance(callback) {
|
|||||||
return this.getBalanceByAddress(null, callback);
|
return this.getBalanceByAddress(null, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
TXPool.prototype.addUnchecked = function addUnchecked(tx, callback, force) {
|
||||||
|
var self = this;
|
||||||
|
var prefix = this.prefix + '/';
|
||||||
|
var hash = tx.hash('hex');
|
||||||
|
var batch;
|
||||||
|
|
||||||
|
var unlock = this._lock(addUnchecked, [tx, callback], force);
|
||||||
|
if (!unlock)
|
||||||
|
return;
|
||||||
|
|
||||||
|
callback = utils.wrap(callback, unlock);
|
||||||
|
|
||||||
|
batch = this.db.batch();
|
||||||
|
|
||||||
|
batch.put(prefix + 't/t/' + hash, tx.toExtended());
|
||||||
|
|
||||||
|
tx.getAddresses().forEach(function(address) {
|
||||||
|
batch.put(prefix + 't/a/' + address + '/' + hash, DUMMY);
|
||||||
|
});
|
||||||
|
|
||||||
|
tx.inputs.forEach(function(input) {
|
||||||
|
var key = input.prevout.hash + '/' + input.prevout.index;
|
||||||
|
var address;
|
||||||
|
|
||||||
|
if (tx.isCoinbase())
|
||||||
|
return;
|
||||||
|
|
||||||
|
assert(input.output);
|
||||||
|
address = input.getAddress();
|
||||||
|
|
||||||
|
batch.del(prefix + 'u/t/' + key);
|
||||||
|
batch.put(prefix + 's/t/' + key, tx.hash());
|
||||||
|
|
||||||
|
if (address)
|
||||||
|
batch.del(prefix + 'u/a/' + address + '/' + key);
|
||||||
|
});
|
||||||
|
|
||||||
|
tx.outputs.forEach(function(output, i) {
|
||||||
|
var key = hash + '/' + i;
|
||||||
|
var address = output.getAddress();
|
||||||
|
var coin = bcoin.coin(tx, i).toRaw();
|
||||||
|
batch.put(prefix + 'u/t/' + key, coin);
|
||||||
|
|
||||||
|
if (address)
|
||||||
|
batch.put(prefix + 'u/a/' + address + '/' + key, DUMMY);
|
||||||
|
});
|
||||||
|
|
||||||
|
return batch.write(function(err) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
self.emit('add tx', tx);
|
||||||
|
return callback();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
TXPool.prototype.removeUnchecked = function removeUnchecked(tx, callback, force) {
|
||||||
|
var self = this;
|
||||||
|
var prefix = this.prefix + '/';
|
||||||
|
var hash = tx.hash('hex');
|
||||||
|
var batch;
|
||||||
|
|
||||||
|
var unlock = this._lock(removeUnchecked, [tx, callback], force);
|
||||||
|
if (!unlock)
|
||||||
|
return;
|
||||||
|
|
||||||
|
callback = utils.wrap(callback, unlock);
|
||||||
|
|
||||||
|
batch = this.db.batch();
|
||||||
|
|
||||||
|
batch.del(prefix + 't/t/' + hash);
|
||||||
|
batch.del(prefix + 'D/' + hash);
|
||||||
|
|
||||||
|
tx.getAddresses().forEach(function(address) {
|
||||||
|
batch.del(prefix + 't/a/' + address + '/' + hash);
|
||||||
|
});
|
||||||
|
|
||||||
|
tx.inputs.forEach(function(input) {
|
||||||
|
var key = input.prevout.hash + '/' + input.prevout.index;
|
||||||
|
var address;
|
||||||
|
|
||||||
|
if (tx.isCoinbase())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!input.output)
|
||||||
|
return;
|
||||||
|
|
||||||
|
address = input.getAddress();
|
||||||
|
|
||||||
|
batch.del(prefix + 'u/t/' + key);
|
||||||
|
batch.del(prefix + 's/t/' + key);
|
||||||
|
|
||||||
|
if (address)
|
||||||
|
batch.del(prefix + 'u/a/' + address + '/' + key);
|
||||||
|
});
|
||||||
|
|
||||||
|
tx.outputs.forEach(function(output, i) {
|
||||||
|
var key = hash + '/' + i;
|
||||||
|
var address = output.getAddress();
|
||||||
|
|
||||||
|
batch.del(prefix + 'u/t/' + key);
|
||||||
|
|
||||||
|
if (address)
|
||||||
|
batch.del(prefix + 'u/a/' + address + '/' + key);
|
||||||
|
});
|
||||||
|
|
||||||
|
batch.write(function(err) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
self.emit('remove tx', tx);
|
||||||
|
return callback();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
TXPool.prototype.zap = function zap(tip, callback, force) {
|
||||||
|
var self = this;
|
||||||
|
var now = tip.ts;
|
||||||
|
var age = 10 * 60 * 60;
|
||||||
|
|
||||||
|
var unlock = this._lock(zap, [tip, callback], force);
|
||||||
|
if (!unlock)
|
||||||
|
return;
|
||||||
|
|
||||||
|
callback = utils.wrap(callback, unlock);
|
||||||
|
|
||||||
|
// return this.getRange(null, {
|
||||||
|
// start: 0,
|
||||||
|
// end: now - age
|
||||||
|
// }, function(err, txs) {
|
||||||
|
// });
|
||||||
|
|
||||||
|
this.getPending(function(err, txs) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
|
||||||
|
txs = txs.filter(function(tx) {
|
||||||
|
return now > tx.ps + age;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (txs.length === 0)
|
||||||
|
return callback();
|
||||||
|
|
||||||
|
self.fillTX(txs, function(err) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
|
||||||
|
utils.forEachSerial(txs, function(tx, next) {
|
||||||
|
self.lazyRemove(tx, next);
|
||||||
|
}, callback);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expose
|
* Expose
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -19,6 +19,7 @@ var dummyInput = {
|
|||||||
index: 0
|
index: 0
|
||||||
},
|
},
|
||||||
script: new bcoin.script([]),
|
script: new bcoin.script([]),
|
||||||
|
witness: new bcoin.script.witness([]),
|
||||||
sequence: 0xffffffff
|
sequence: 0xffffffff
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -131,11 +132,13 @@ describe('Wallet', function() {
|
|||||||
assert(tx.verify());
|
assert(tx.verify());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var dw, di;
|
||||||
it('should have TX pool and be serializable', function(cb) {
|
it('should have TX pool and be serializable', function(cb) {
|
||||||
wdb.create({}, function(err, w) {
|
wdb.create({}, function(err, w) {
|
||||||
assert.noError(err);
|
assert.noError(err);
|
||||||
wdb.create({}, function(err, f) {
|
wdb.create({}, function(err, f) {
|
||||||
assert.noError(err);
|
assert.noError(err);
|
||||||
|
dw = w;
|
||||||
|
|
||||||
// Coinbase
|
// Coinbase
|
||||||
var t1 = bcoin.mtx().addOutput(w, 50000).addOutput(w, 1000);
|
var t1 = bcoin.mtx().addOutput(w, 50000).addOutput(w, 1000);
|
||||||
@ -145,6 +148,7 @@ describe('Wallet', function() {
|
|||||||
var t2 = bcoin.mtx().addInput(t1, 0) // 50000
|
var t2 = bcoin.mtx().addInput(t1, 0) // 50000
|
||||||
.addOutput(w, 24000)
|
.addOutput(w, 24000)
|
||||||
.addOutput(w, 24000);
|
.addOutput(w, 24000);
|
||||||
|
di = t2.inputs[0];
|
||||||
// balance: 49000
|
// balance: 49000
|
||||||
w.sign(t2);
|
w.sign(t2);
|
||||||
var t3 = bcoin.mtx().addInput(t1, 1) // 1000
|
var t3 = bcoin.mtx().addInput(t1, 1) // 1000
|
||||||
@ -179,8 +183,6 @@ describe('Wallet', function() {
|
|||||||
fake.hint = 'fake';
|
fake.hint = 'fake';
|
||||||
|
|
||||||
// Fake TX should temporarly change output
|
// Fake TX should temporarly change output
|
||||||
// wdb.addTX(fake);
|
|
||||||
|
|
||||||
wdb.addTX(fake, function(err) {
|
wdb.addTX(fake, function(err) {
|
||||||
assert.noError(err);
|
assert.noError(err);
|
||||||
wdb.addTX(t4, function(err) {
|
wdb.addTX(t4, function(err) {
|
||||||
@ -234,6 +236,19 @@ describe('Wallet', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should cleanup spenders after double-spend', function(cb) {
|
||||||
|
var t1 = bcoin.mtx().addOutput(dw, 5000);
|
||||||
|
t1.addInput(di);
|
||||||
|
wdb.addTX(t1, function(err) {
|
||||||
|
assert.noError(err);
|
||||||
|
dw.getBalance(function(err, balance) {
|
||||||
|
assert.noError(err);
|
||||||
|
assert.equal(balance.toString(10), '11000');
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should fill tx with inputs', function(cb) {
|
it('should fill tx with inputs', function(cb) {
|
||||||
wdb.create({}, function(err, w1) {
|
wdb.create({}, function(err, w1) {
|
||||||
assert.noError(err);
|
assert.noError(err);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user