txdb: fix double-spend handling.
This commit is contained in:
parent
a6ca11611f
commit
af33e61031
@ -73,38 +73,18 @@ TXDB.prototype._lock = function _lock(func, args, force) {
|
|||||||
|
|
||||||
TXDB.prototype._loadFilter = function loadFilter(callback) {
|
TXDB.prototype._loadFilter = function loadFilter(callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var iter;
|
|
||||||
|
|
||||||
if (!this.filter)
|
if (!this.filter)
|
||||||
return callback();
|
return callback();
|
||||||
|
|
||||||
iter = this.db.iterator({
|
this.db.iterate({
|
||||||
gte: 'W',
|
gte: 'W',
|
||||||
lte: 'W~',
|
lte: 'W~',
|
||||||
keys: true,
|
transform: function(key) {
|
||||||
values: false,
|
|
||||||
fillCache: false,
|
|
||||||
keyAsBuffer: false
|
|
||||||
});
|
|
||||||
|
|
||||||
(function next() {
|
|
||||||
iter.next(function(err, key, value) {
|
|
||||||
if (err) {
|
|
||||||
return iter.end(function() {
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === undefined)
|
|
||||||
return iter.end(callback);
|
|
||||||
|
|
||||||
key = key.split('/')[1];
|
key = key.split('/')[1];
|
||||||
|
|
||||||
self.filter.add(key, 'hex');
|
self.filter.add(key, 'hex');
|
||||||
|
}
|
||||||
next();
|
}, callback);
|
||||||
});
|
|
||||||
})();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
TXDB.prototype._testFilter = function _testFilter(addresses) {
|
TXDB.prototype._testFilter = function _testFilter(addresses) {
|
||||||
@ -297,12 +277,6 @@ TXDB.prototype._getOrphans = function _getOrphans(key, callback) {
|
|||||||
TXDB.prototype.add = function add(tx, callback, force) {
|
TXDB.prototype.add = function add(tx, callback, force) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if (Array.isArray(tx)) {
|
|
||||||
return utils.forEachSerial(tx, function(tx, next) {
|
|
||||||
self.add(tx, next, force);
|
|
||||||
}, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.getMap(tx, function(err, map) {
|
return this.getMap(tx, function(err, map) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
@ -314,18 +288,13 @@ TXDB.prototype.add = function add(tx, callback, force) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// This big scary function is what a persistent tx pool
|
|
||||||
// looks like. It's a semi mempool in that it can handle
|
|
||||||
// receiving txs out of order.
|
|
||||||
TXDB.prototype._add = function add(tx, map, callback, force) {
|
TXDB.prototype._add = function add(tx, map, callback, force) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var hash = tx.hash('hex');
|
|
||||||
var updated = false;
|
var updated = false;
|
||||||
var batch;
|
var batch, hash, i, j, unlock, path, paths, id;
|
||||||
|
|
||||||
assert(tx.ts > 0 || tx.ps > 0);
|
unlock = this._lock(add, [tx, map, callback], force);
|
||||||
|
|
||||||
var unlock = this._lock(add, [tx, map, callback], force);
|
|
||||||
if (!unlock)
|
if (!unlock)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -341,14 +310,15 @@ TXDB.prototype._add = function add(tx, map, callback, force) {
|
|||||||
|
|
||||||
// Ignore if we already have this tx.
|
// Ignore if we already have this tx.
|
||||||
if (existing)
|
if (existing)
|
||||||
return callback(null, true);
|
return callback(null, true, map);
|
||||||
|
|
||||||
|
hash = tx.hash('hex');
|
||||||
|
|
||||||
batch = self.db.batch();
|
batch = self.db.batch();
|
||||||
|
|
||||||
batch.put('t/' + hash, tx.toExtended());
|
batch.put('t/' + hash, tx.toExtended());
|
||||||
|
|
||||||
if (tx.ts === 0) {
|
if (tx.ts === 0) {
|
||||||
assert(tx.ps > 0);
|
|
||||||
batch.put('p/' + hash, DUMMY);
|
batch.put('p/' + hash, DUMMY);
|
||||||
batch.put('m/' + pad32(tx.ps) + '/' + hash, DUMMY);
|
batch.put('m/' + pad32(tx.ps) + '/' + hash, DUMMY);
|
||||||
} else {
|
} else {
|
||||||
@ -356,8 +326,9 @@ TXDB.prototype._add = function add(tx, map, callback, force) {
|
|||||||
batch.put('m/' + pad32(tx.ts) + '/' + hash, DUMMY);
|
batch.put('m/' + pad32(tx.ts) + '/' + hash, DUMMY);
|
||||||
}
|
}
|
||||||
|
|
||||||
map.all.forEach(function(path) {
|
for (i = 0; i < map.all.length; i++) {
|
||||||
var id = path.id + '/' + path.account;
|
path = map.all[i];
|
||||||
|
id = path.id + '/' + path.account;
|
||||||
batch.put('T/' + id + '/' + hash, DUMMY);
|
batch.put('T/' + id + '/' + hash, DUMMY);
|
||||||
if (tx.ts === 0) {
|
if (tx.ts === 0) {
|
||||||
batch.put('P/' + id + '/' + hash, DUMMY);
|
batch.put('P/' + id + '/' + hash, DUMMY);
|
||||||
@ -366,12 +337,12 @@ TXDB.prototype._add = function add(tx, map, callback, force) {
|
|||||||
batch.put('H/' + id + '/' + pad32(tx.height) + '/' + hash, DUMMY);
|
batch.put('H/' + id + '/' + pad32(tx.height) + '/' + hash, DUMMY);
|
||||||
batch.put('M/' + id + '/' + pad32(tx.ts) + '/' + hash, DUMMY);
|
batch.put('M/' + 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, address;
|
|
||||||
var prevout = input.prevout;
|
var prevout = input.prevout;
|
||||||
|
var key, address;
|
||||||
|
|
||||||
if (tx.isCoinbase())
|
if (tx.isCoinbase())
|
||||||
return next();
|
return next();
|
||||||
@ -388,6 +359,8 @@ TXDB.prototype._add = function add(tx, map, callback, force) {
|
|||||||
|
|
||||||
key = prevout.hash + '/' + prevout.index;
|
key = prevout.hash + '/' + prevout.index;
|
||||||
|
|
||||||
|
batch.put('s/' + key, tx.hash());
|
||||||
|
|
||||||
if (coin) {
|
if (coin) {
|
||||||
// Add TX to inputs and spend money
|
// Add TX to inputs and spend money
|
||||||
input.coin = coin;
|
input.coin = coin;
|
||||||
@ -400,28 +373,28 @@ TXDB.prototype._add = function add(tx, map, callback, force) {
|
|||||||
|
|
||||||
updated = true;
|
updated = true;
|
||||||
|
|
||||||
if (address) {
|
paths = map.table[address];
|
||||||
map.table[address].forEach(function(path) {
|
|
||||||
var id = path.id + '/' + path.account;
|
for (j = 0; j < paths.length; j++) {
|
||||||
batch.del('C/' + id + '/' + key);
|
path = paths[j];
|
||||||
});
|
id = path.id + '/' + path.account;
|
||||||
|
batch.del('C/' + id + '/' + key);
|
||||||
}
|
}
|
||||||
|
|
||||||
batch.del('c/' + key);
|
batch.del('c/' + key);
|
||||||
batch.put('s/' + key, tx.hash());
|
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
input.coin = null;
|
input.coin = null;
|
||||||
|
|
||||||
self.isSpent(prevout.hash, prevout.index, function(err, spentBy) {
|
self.isSpent(prevout.hash, prevout.index, function(err, spent) {
|
||||||
if (err)
|
if (err)
|
||||||
return next(err);
|
return next(err);
|
||||||
|
|
||||||
// Are we double-spending?
|
// Are we double-spending?
|
||||||
// Replace older txs with newer ones.
|
// Replace older txs with newer ones.
|
||||||
if (spentBy) {
|
if (spent) {
|
||||||
return self.getTX(prevout.hash, function(err, prev) {
|
return self.getTX(prevout.hash, function(err, prev) {
|
||||||
if (err)
|
if (err)
|
||||||
return next(err);
|
return next(err);
|
||||||
@ -434,19 +407,22 @@ TXDB.prototype._add = function add(tx, map, callback, force) {
|
|||||||
// Skip invalid transactions
|
// Skip invalid transactions
|
||||||
if (self.options.verify) {
|
if (self.options.verify) {
|
||||||
if (!tx.verify(i))
|
if (!tx.verify(i))
|
||||||
return callback(null, false);
|
return callback(null, false, map);
|
||||||
}
|
}
|
||||||
|
|
||||||
return self._removeSpenders(spentBy, tx, function(err, result) {
|
return self._removeConflict(spent, tx, function(err, rtx, rmap) {
|
||||||
if (err)
|
if (err)
|
||||||
return next(err);
|
return next(err);
|
||||||
|
|
||||||
if (!result) {
|
// Spender was not removed, the current
|
||||||
assert(tx.ts === 0, 'I\'m confused');
|
// transaction is not elligible to be added.
|
||||||
return callback(null, false);
|
if (!rtx)
|
||||||
}
|
return callback(null, false, map);
|
||||||
|
|
||||||
|
self.emit('conflict', rtx, rmap);
|
||||||
|
|
||||||
batch.clear();
|
batch.clear();
|
||||||
|
|
||||||
self._add(tx, map, callback, true);
|
self._add(tx, map, callback, true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -470,14 +446,13 @@ TXDB.prototype._add = function add(tx, map, callback, force) {
|
|||||||
// Add unspent outputs or resolve orphans
|
// Add unspent outputs or resolve orphans
|
||||||
utils.forEachSerial(tx.outputs, function(output, next, i) {
|
utils.forEachSerial(tx.outputs, function(output, next, i) {
|
||||||
var address = output.getHash();
|
var address = output.getHash();
|
||||||
var key, coin;
|
var key = hash + '/' + i;
|
||||||
|
var coin;
|
||||||
|
|
||||||
// Do not add unspents for outputs that aren't ours.
|
// Do not add unspents for outputs that aren't ours.
|
||||||
if (!address || !map.table[address].length)
|
if (!address || !map.table[address].length)
|
||||||
return next();
|
return next();
|
||||||
|
|
||||||
key = hash + '/' + i;
|
|
||||||
|
|
||||||
if (output.script.isUnspendable())
|
if (output.script.isUnspendable())
|
||||||
return next();
|
return next();
|
||||||
|
|
||||||
@ -518,11 +493,7 @@ TXDB.prototype._add = function add(tx, map, callback, force) {
|
|||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.lazyRemove(orphan.tx, function(err) {
|
self.lazyRemove(orphan.tx, next, true);
|
||||||
if (err)
|
|
||||||
return next(err);
|
|
||||||
return next();
|
|
||||||
}, true);
|
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
if (err)
|
if (err)
|
||||||
return next(err);
|
return next(err);
|
||||||
@ -538,11 +509,12 @@ TXDB.prototype._add = function add(tx, map, callback, force) {
|
|||||||
return next(err);
|
return next(err);
|
||||||
|
|
||||||
if (!orphans) {
|
if (!orphans) {
|
||||||
if (address) {
|
paths = map.table[address];
|
||||||
map.table[address].forEach(function(path) {
|
|
||||||
var id = path.id + '/' + path.account;
|
for (j = 0; j < paths.length; j++) {
|
||||||
batch.put('C/' + id + '/' + key, DUMMY);
|
path = paths[j];
|
||||||
});
|
id = path.id + '/' + path.account;
|
||||||
|
batch.put('C/' + id + '/' + key, DUMMY);
|
||||||
}
|
}
|
||||||
|
|
||||||
batch.put('c/' + key, coin.toRaw());
|
batch.put('c/' + key, coin.toRaw());
|
||||||
@ -573,7 +545,7 @@ TXDB.prototype._add = function add(tx, map, callback, force) {
|
|||||||
self.emit('updated', tx, map);
|
self.emit('updated', tx, map);
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback(null, true);
|
return callback(null, true, map);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -593,8 +565,9 @@ TXDB.prototype._add = function add(tx, map, callback, force) {
|
|||||||
* @param {Function} callback - Returns [Error, Boolean].
|
* @param {Function} callback - Returns [Error, Boolean].
|
||||||
*/
|
*/
|
||||||
|
|
||||||
TXDB.prototype._removeSpenders = function removeSpenders(hash, ref, callback) {
|
TXDB.prototype._removeConflict = function _removeConflict(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)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
@ -602,32 +575,77 @@ TXDB.prototype._removeSpenders = function removeSpenders(hash, ref, callback) {
|
|||||||
if (!tx)
|
if (!tx)
|
||||||
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(null, false);
|
// If spender is confirmed and replacement
|
||||||
|
// is not confirmed, do nothing.
|
||||||
|
if (ref.ts === 0)
|
||||||
|
return callback();
|
||||||
|
|
||||||
if (ref.ts === 0 && ref.ps < tx.ps)
|
// If both are confirmed but replacement
|
||||||
return callback(null, false);
|
// is older than spender, do nothing.
|
||||||
|
if (ref.ts < tx.ts)
|
||||||
|
return callback();
|
||||||
|
} else {
|
||||||
|
// If spender is unconfirmed and replacement
|
||||||
|
// is confirmed, do nothing.
|
||||||
|
if (ref.ts !== 0)
|
||||||
|
return callback();
|
||||||
|
|
||||||
utils.forEachSerial(tx.outputs, function(output, next, i) {
|
// If both are unconfirmed but replacement
|
||||||
self.isSpent(hash, i, function(err, spent) {
|
// is older than spender, do nothing.
|
||||||
if (err)
|
if (ref.ps < tx.ps)
|
||||||
return next(err);
|
return callback();
|
||||||
if (spent)
|
}
|
||||||
return self._removeSpenders(spent, ref, next);
|
|
||||||
next();
|
self._removeRecursive(tx, function(err, result, map) {
|
||||||
});
|
|
||||||
}, function(err) {
|
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
return self.lazyRemove(tx, function(err) {
|
return callback(null, tx, map);
|
||||||
if (err)
|
|
||||||
return callback(err);
|
|
||||||
return callback(null, true);
|
|
||||||
}, true);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a transaction and recursively
|
||||||
|
* remove all of its spenders.
|
||||||
|
* @private
|
||||||
|
* @param {TX} tx - Transaction to be removed.
|
||||||
|
* @param {Function} callback - Returns [Error, Boolean].
|
||||||
|
*/
|
||||||
|
|
||||||
|
TXDB.prototype._removeRecursive = function _removeRecursive(tx, callback) {
|
||||||
|
var self = this;
|
||||||
|
var hash = tx.hash('hex');
|
||||||
|
|
||||||
|
utils.forEachSerial(tx.outputs, function(output, next, i) {
|
||||||
|
self.isSpent(hash, i, function(err, spent) {
|
||||||
|
if (err)
|
||||||
|
return next(err);
|
||||||
|
|
||||||
|
// Remove all of the spender's spenders first.
|
||||||
|
if (spent) {
|
||||||
|
return self.getTX(spent, function(err, tx) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
|
||||||
|
if (!tx)
|
||||||
|
return callback(new Error('Could not find spender.'));
|
||||||
|
|
||||||
|
return self._removeRecursive(tx, next);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}, function(err) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
|
||||||
|
// Remove the spender.
|
||||||
|
return self.lazyRemove(tx, callback, true);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test an entire transaction to see
|
* Test an entire transaction to see
|
||||||
* if any of its outpoints are a double-spend.
|
* if any of its outpoints are a double-spend.
|
||||||
@ -637,6 +655,7 @@ TXDB.prototype._removeSpenders = function removeSpenders(hash, ref, callback) {
|
|||||||
|
|
||||||
TXDB.prototype.isDoubleSpend = function isDoubleSpend(tx, callback) {
|
TXDB.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) {
|
||||||
self.isSpent(input.prevout.hash, input.prevout.index, function(err, spent) {
|
self.isSpent(input.prevout.hash, input.prevout.index, function(err, spent) {
|
||||||
if (err)
|
if (err)
|
||||||
@ -676,30 +695,33 @@ TXDB.prototype.isSpent = function isSpent(hash, index, callback) {
|
|||||||
|
|
||||||
TXDB.prototype._confirm = function _confirm(tx, map, callback, force) {
|
TXDB.prototype._confirm = function _confirm(tx, map, callback, force) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var hash = tx.hash('hex');
|
var hash, batch, unlock, i, path, id;
|
||||||
var batch;
|
|
||||||
|
unlock = this._lock(_confirm, [tx, map, callback], force);
|
||||||
|
|
||||||
var unlock = this._lock(_confirm, [tx, map, callback], force);
|
|
||||||
if (!unlock)
|
if (!unlock)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
callback = utils.wrap(callback, unlock);
|
callback = utils.wrap(callback, unlock);
|
||||||
|
|
||||||
|
hash = tx.hash('hex');
|
||||||
|
|
||||||
this.getTX(hash, function(err, existing) {
|
this.getTX(hash, function(err, existing) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
// Haven't seen this tx before, add it.
|
// Haven't seen this tx before, add it.
|
||||||
if (!existing)
|
if (!existing)
|
||||||
return callback(null, false);
|
return callback(null, false, map);
|
||||||
|
|
||||||
// Existing tx is already confirmed. Ignore.
|
// Existing tx is already confirmed. Ignore.
|
||||||
if (existing.ts !== 0)
|
if (existing.ts !== 0)
|
||||||
return callback(null, true);
|
return callback(null, true, map);
|
||||||
|
|
||||||
// The incoming tx won't confirm the existing one anyway. Ignore.
|
// The incoming tx won't confirm the
|
||||||
|
// existing one anyway. Ignore.
|
||||||
if (tx.ts === 0)
|
if (tx.ts === 0)
|
||||||
return callback(null, true);
|
return callback(null, true, map);
|
||||||
|
|
||||||
batch = self.db.batch();
|
batch = self.db.batch();
|
||||||
|
|
||||||
@ -715,13 +737,14 @@ TXDB.prototype._confirm = function _confirm(tx, map, callback, force) {
|
|||||||
batch.del('m/' + pad32(existing.ps) + '/' + hash);
|
batch.del('m/' + pad32(existing.ps) + '/' + hash);
|
||||||
batch.put('m/' + pad32(tx.ts) + '/' + hash, DUMMY);
|
batch.put('m/' + pad32(tx.ts) + '/' + hash, DUMMY);
|
||||||
|
|
||||||
map.all.forEach(function(path) {
|
for (i = 0; i < map.all.length; i++) {
|
||||||
var id = path.id + '/' + path.account;
|
path = map.all[i];
|
||||||
|
id = path.id + '/' + path.account;
|
||||||
batch.del('P/' + id + '/' + hash);
|
batch.del('P/' + id + '/' + hash);
|
||||||
batch.put('H/' + id + '/' + pad32(tx.height) + '/' + hash, DUMMY);
|
batch.put('H/' + id + '/' + pad32(tx.height) + '/' + hash, DUMMY);
|
||||||
batch.del('M/' + id + '/' + pad32(existing.ps) + '/' + hash);
|
batch.del('M/' + id + '/' + pad32(existing.ps) + '/' + hash);
|
||||||
batch.put('M/' + id + '/' + pad32(tx.ts) + '/' + hash, DUMMY);
|
batch.put('M/' + id + '/' + pad32(tx.ts) + '/' + hash, DUMMY);
|
||||||
});
|
}
|
||||||
|
|
||||||
utils.forEachSerial(tx.outputs, function(output, next, i) {
|
utils.forEachSerial(tx.outputs, function(output, next, i) {
|
||||||
var address = output.getHash();
|
var address = output.getHash();
|
||||||
@ -758,7 +781,7 @@ TXDB.prototype._confirm = function _confirm(tx, map, callback, force) {
|
|||||||
self.emit('confirmed', tx, map);
|
self.emit('confirmed', tx, map);
|
||||||
self.emit('tx', tx, map);
|
self.emit('tx', tx, map);
|
||||||
|
|
||||||
return callback(null, true);
|
return callback(null, true, map);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -774,12 +797,6 @@ TXDB.prototype._confirm = function _confirm(tx, map, callback, force) {
|
|||||||
TXDB.prototype.remove = function remove(hash, callback, force) {
|
TXDB.prototype.remove = function remove(hash, callback, force) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if (Array.isArray(hash)) {
|
|
||||||
return utils.forEachSerial(hash, function(hash, next) {
|
|
||||||
self.remove(hash, next, force);
|
|
||||||
}, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hash.hash)
|
if (hash.hash)
|
||||||
hash = hash.hash('hex');
|
hash = hash.hash('hex');
|
||||||
|
|
||||||
@ -815,12 +832,6 @@ TXDB.prototype.remove = function remove(hash, callback, force) {
|
|||||||
TXDB.prototype.lazyRemove = function lazyRemove(tx, callback, force) {
|
TXDB.prototype.lazyRemove = function lazyRemove(tx, callback, force) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if (Array.isArray(tx)) {
|
|
||||||
return utils.forEachSerial(tx, function(tx, next) {
|
|
||||||
self.lazyRemove(tx, next, force);
|
|
||||||
}, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.getMap(tx, function(err, map) {
|
return this.getMap(tx, function(err, map) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
@ -842,15 +853,17 @@ TXDB.prototype.lazyRemove = function lazyRemove(tx, callback, force) {
|
|||||||
|
|
||||||
TXDB.prototype._remove = function remove(tx, map, callback, force) {
|
TXDB.prototype._remove = function remove(tx, map, callback, force) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var hash = tx.hash('hex');
|
var unlock, hash, batch, i, j, path, id, key, paths, address, input, output;
|
||||||
var batch;
|
|
||||||
|
unlock = this._lock(remove, [tx, map, callback], force);
|
||||||
|
|
||||||
var unlock = this._lock(remove, [tx, map, callback], force);
|
|
||||||
if (!unlock)
|
if (!unlock)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
callback = utils.wrap(callback, unlock);
|
callback = utils.wrap(callback, unlock);
|
||||||
|
|
||||||
|
hash = tx.hash('hex');
|
||||||
|
|
||||||
batch = this.db.batch();
|
batch = this.db.batch();
|
||||||
|
|
||||||
batch.del('t/' + hash);
|
batch.del('t/' + hash);
|
||||||
@ -863,8 +876,9 @@ TXDB.prototype._remove = function remove(tx, map, callback, force) {
|
|||||||
batch.del('m/' + pad32(tx.ts) + '/' + hash);
|
batch.del('m/' + pad32(tx.ts) + '/' + hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
map.all.forEach(function(path) {
|
for (i = 0; i < map.all.length; i++) {
|
||||||
var id = path.id + '/' + path.account;
|
path = map.all[i];
|
||||||
|
id = path.id + '/' + path.account;
|
||||||
batch.del('T/' + id + '/' + hash);
|
batch.del('T/' + id + '/' + hash);
|
||||||
if (tx.ts === 0) {
|
if (tx.ts === 0) {
|
||||||
batch.del('P/' + id + '/' + hash);
|
batch.del('P/' + id + '/' + hash);
|
||||||
@ -873,56 +887,60 @@ TXDB.prototype._remove = function remove(tx, map, callback, force) {
|
|||||||
batch.del('H/' + id + '/' + pad32(tx.height) + '/' + hash);
|
batch.del('H/' + id + '/' + pad32(tx.height) + '/' + hash);
|
||||||
batch.del('M/' + id + '/' + pad32(tx.ts) + '/' + hash);
|
batch.del('M/' + id + '/' + pad32(tx.ts) + '/' + hash);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
this.fillHistory(tx, function(err) {
|
this.fillHistory(tx, function(err) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
tx.inputs.forEach(function(input) {
|
for (i = 0; i < tx.inputs.length; i++) {
|
||||||
var key = input.prevout.hash + '/' + input.prevout.index;
|
input = tx.inputs[i];
|
||||||
var address = input.getHash();
|
key = input.prevout.hash + '/' + input.prevout.index;
|
||||||
|
address = input.getHash();
|
||||||
|
|
||||||
if (tx.isCoinbase())
|
if (tx.isCoinbase())
|
||||||
return;
|
break;
|
||||||
|
|
||||||
if (!input.coin)
|
if (!input.coin)
|
||||||
return;
|
continue;
|
||||||
|
|
||||||
if (!address || !map.table[address].length)
|
if (!address || !map.table[address].length)
|
||||||
return;
|
continue;
|
||||||
|
|
||||||
if (address) {
|
paths = map.table[address];
|
||||||
map.table[address].forEach(function(path) {
|
|
||||||
var id = path.id + '/' + path.account;
|
for (j = 0; j < paths.length; j++) {
|
||||||
batch.put('C/' + id + '/' + key, DUMMY);
|
path = paths[j];
|
||||||
});
|
id = path.id + '/' + path.account;
|
||||||
|
batch.put('C/' + id + '/' + key, DUMMY);
|
||||||
}
|
}
|
||||||
|
|
||||||
batch.put('c/' + key, input.coin.toRaw());
|
batch.put('c/' + key, input.coin.toRaw());
|
||||||
batch.del('s/' + key);
|
batch.del('s/' + key);
|
||||||
batch.del('o/' + key);
|
batch.del('o/' + key);
|
||||||
});
|
}
|
||||||
|
|
||||||
tx.outputs.forEach(function(output, i) {
|
for (i = 0; i < tx.outputs.length; i++) {
|
||||||
var key = hash + '/' + i;
|
output = tx.outputs[i];
|
||||||
var address = output.getHash();
|
key = hash + '/' + i;
|
||||||
|
address = output.getHash();
|
||||||
|
|
||||||
if (!address || !map.table[address].length)
|
if (!address || !map.table[address].length)
|
||||||
return;
|
continue;
|
||||||
|
|
||||||
if (output.script.isUnspendable())
|
if (output.script.isUnspendable())
|
||||||
return;
|
continue;
|
||||||
|
|
||||||
if (address) {
|
paths = map.table[address];
|
||||||
map.table[address].forEach(function(path) {
|
|
||||||
var id = path.id + '/' + path.account;
|
for (j = 0; j < paths.length; j++) {
|
||||||
batch.del('C/' + id + '/' + key);
|
path = paths[j];
|
||||||
});
|
id = path.id + '/' + path.account;
|
||||||
|
batch.del('C/' + id + '/' + key);
|
||||||
}
|
}
|
||||||
|
|
||||||
batch.del('c/' + key);
|
batch.del('c/' + key);
|
||||||
});
|
}
|
||||||
|
|
||||||
batch.write(function(err) {
|
batch.write(function(err) {
|
||||||
if (err)
|
if (err)
|
||||||
@ -930,7 +948,7 @@ TXDB.prototype._remove = function remove(tx, map, callback, force) {
|
|||||||
|
|
||||||
self.emit('remove tx', tx, map);
|
self.emit('remove tx', tx, map);
|
||||||
|
|
||||||
return callback(null, true);
|
return callback(null, true, map);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -944,12 +962,6 @@ TXDB.prototype._remove = function remove(tx, map, callback, force) {
|
|||||||
TXDB.prototype.unconfirm = function unconfirm(hash, callback, force) {
|
TXDB.prototype.unconfirm = function unconfirm(hash, callback, force) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if (Array.isArray(hash)) {
|
|
||||||
return utils.forEachSerial(hash, function(hash, next) {
|
|
||||||
self.unconfirm(hash, next, force);
|
|
||||||
}, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hash.hash)
|
if (hash.hash)
|
||||||
hash = hash.hash('hex');
|
hash = hash.hash('hex');
|
||||||
|
|
||||||
@ -985,21 +997,23 @@ TXDB.prototype.unconfirm = function unconfirm(hash, callback, force) {
|
|||||||
|
|
||||||
TXDB.prototype._unconfirm = function unconfirm(tx, map, callback, force) {
|
TXDB.prototype._unconfirm = function unconfirm(tx, map, callback, force) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var hash, batch, height, ts;
|
var batch, unlock, hash, height, ts, i, path, id;
|
||||||
|
|
||||||
|
unlock = this._lock(unconfirm, [tx, map, callback], force);
|
||||||
|
|
||||||
var unlock = this._lock(unconfirm, [tx, map, callback], force);
|
|
||||||
if (!unlock)
|
if (!unlock)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
callback = utils.wrap(callback, unlock);
|
callback = utils.wrap(callback, unlock);
|
||||||
|
|
||||||
hash = tx.hash('hex');
|
hash = tx.hash('hex');
|
||||||
batch = this.db.batch();
|
|
||||||
height = tx.height;
|
height = tx.height;
|
||||||
ts = tx.ts;
|
ts = tx.ts;
|
||||||
|
|
||||||
|
batch = this.db.batch();
|
||||||
|
|
||||||
if (height !== -1)
|
if (height !== -1)
|
||||||
return callback(null, false);
|
return callback(null, false, map);
|
||||||
|
|
||||||
tx.height = -1;
|
tx.height = -1;
|
||||||
tx.ps = utils.now();
|
tx.ps = utils.now();
|
||||||
@ -1014,13 +1028,14 @@ TXDB.prototype._unconfirm = function unconfirm(tx, map, callback, force) {
|
|||||||
batch.del('m/' + pad32(ts) + '/' + hash);
|
batch.del('m/' + pad32(ts) + '/' + hash);
|
||||||
batch.put('m/' + pad32(tx.ps) + '/' + hash, DUMMY);
|
batch.put('m/' + pad32(tx.ps) + '/' + hash, DUMMY);
|
||||||
|
|
||||||
map.all.forEach(function(path) {
|
for (i = 0; i < map.all.length; i++) {
|
||||||
var id = path.id + '/' + path.account;
|
path = map.all[i];
|
||||||
|
id = path.id + '/' + path.account;
|
||||||
batch.put('P/' + id + '/' + hash, DUMMY);
|
batch.put('P/' + id + '/' + hash, DUMMY);
|
||||||
batch.del('H/' + id + '/' + pad32(height) + '/' + hash);
|
batch.del('H/' + id + '/' + pad32(height) + '/' + hash);
|
||||||
batch.del('M/' + id + '/' + pad32(ts) + '/' + hash);
|
batch.del('M/' + id + '/' + pad32(ts) + '/' + hash);
|
||||||
batch.put('M/' + id + '/' + pad32(tx.ps) + '/' + hash, DUMMY);
|
batch.put('M/' + id + '/' + pad32(tx.ps) + '/' + hash, DUMMY);
|
||||||
});
|
}
|
||||||
|
|
||||||
utils.forEachSerial(tx.outputs, function(output, next, i) {
|
utils.forEachSerial(tx.outputs, function(output, next, i) {
|
||||||
self.getCoin(hash, i, function(err, coin) {
|
self.getCoin(hash, i, function(err, coin) {
|
||||||
@ -1046,7 +1061,7 @@ TXDB.prototype._unconfirm = function unconfirm(tx, map, callback, force) {
|
|||||||
|
|
||||||
self.emit('unconfirmed', tx, map);
|
self.emit('unconfirmed', tx, map);
|
||||||
|
|
||||||
return callback(null, true);
|
return callback(null, true, map);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -1607,6 +1622,7 @@ TXDB.prototype.getBalance = function getBalance(id, callback) {
|
|||||||
|
|
||||||
TXDB.prototype.zap = function zap(id, age, callback, force) {
|
TXDB.prototype.zap = function zap(id, age, callback, force) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
var unlock;
|
||||||
|
|
||||||
if (typeof age === 'function') {
|
if (typeof age === 'function') {
|
||||||
force = callback;
|
force = callback;
|
||||||
@ -1615,7 +1631,8 @@ TXDB.prototype.zap = function zap(id, age, callback, force) {
|
|||||||
id = null;
|
id = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var unlock = this._lock(zap, [id, age, callback], force);
|
unlock = this._lock(zap, [id, age, callback], force);
|
||||||
|
|
||||||
if (!unlock)
|
if (!unlock)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|||||||
@ -2243,10 +2243,10 @@ utils.pad32 = function pad32(num) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
utils.wrap = function wrap(callback, unlock) {
|
utils.wrap = function wrap(callback, unlock) {
|
||||||
return function(err, result) {
|
return function(err, res1, res2) {
|
||||||
unlock();
|
unlock();
|
||||||
if (callback)
|
if (callback)
|
||||||
callback(err, result);
|
callback(err, res1, res2);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -202,6 +202,7 @@ Wallet.prototype.addKey = function addKey(account, key, callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unlock = this.locker.lock(addKey, [account, key, callback]);
|
unlock = this.locker.lock(addKey, [account, key, callback]);
|
||||||
|
|
||||||
if (!unlock)
|
if (!unlock)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -235,6 +236,7 @@ Wallet.prototype.removeKey = function removeKey(account, key, callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unlock = this.locker.lock(removeKey, [account, key, callback]);
|
unlock = this.locker.lock(removeKey, [account, key, callback]);
|
||||||
|
|
||||||
if (!unlock)
|
if (!unlock)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -339,6 +341,7 @@ Wallet.prototype.createAccount = function createAccount(options, callback, force
|
|||||||
var master, key, unlock;
|
var master, key, unlock;
|
||||||
|
|
||||||
unlock = this.locker.lock(createAccount, [options, callback], force);
|
unlock = this.locker.lock(createAccount, [options, callback], force);
|
||||||
|
|
||||||
if (!unlock)
|
if (!unlock)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -396,6 +399,7 @@ Wallet.prototype.getAccounts = function getAccounts(callback) {
|
|||||||
|
|
||||||
Wallet.prototype.getAccount = function getAccount(account, callback, force) {
|
Wallet.prototype.getAccount = function getAccount(account, callback, force) {
|
||||||
var unlock = this.locker.lock(getAccount, [account, callback], force);
|
var unlock = this.locker.lock(getAccount, [account, callback], force);
|
||||||
|
|
||||||
if (!unlock)
|
if (!unlock)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -454,6 +458,7 @@ Wallet.prototype.createAddress = function createAddress(account, change, callbac
|
|||||||
}
|
}
|
||||||
|
|
||||||
unlock = this.locker.lock(createAddress, [account, change, callback]);
|
unlock = this.locker.lock(createAddress, [account, change, callback]);
|
||||||
|
|
||||||
if (!unlock)
|
if (!unlock)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -790,6 +795,7 @@ Wallet.prototype.syncOutputDepth = function syncOutputDepth(tx, callback) {
|
|||||||
var i, path, unlock;
|
var i, path, unlock;
|
||||||
|
|
||||||
unlock = this.locker.lock(syncOutputDepth, [tx, callback]);
|
unlock = this.locker.lock(syncOutputDepth, [tx, callback]);
|
||||||
|
|
||||||
if (!unlock)
|
if (!unlock)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -911,6 +917,7 @@ Wallet.prototype.scan = function scan(getByAddress, callback) {
|
|||||||
var unlock;
|
var unlock;
|
||||||
|
|
||||||
unlock = this.locker.lock(scan, [getByAddress, callback]);
|
unlock = this.locker.lock(scan, [getByAddress, callback]);
|
||||||
|
|
||||||
if (!unlock)
|
if (!unlock)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|||||||
@ -128,6 +128,13 @@ WalletDB.prototype._init = function _init() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.tx.on('conflict', function(tx, map) {
|
||||||
|
self.emit('conflict', tx, map);
|
||||||
|
map.all.forEach(function(path) {
|
||||||
|
self.fire(path.id, 'conflict', tx, path.name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
this.tx.on('confirmed', function(tx, map) {
|
this.tx.on('confirmed', function(tx, map) {
|
||||||
self.emit('confirmed', tx, map);
|
self.emit('confirmed', tx, map);
|
||||||
map.all.forEach(function(path) {
|
map.all.forEach(function(path) {
|
||||||
|
|||||||
@ -192,7 +192,6 @@ describe('Wallet', function() {
|
|||||||
var t1 = bcoin.mtx().addOutput(w, 50000).addOutput(w, 1000);
|
var t1 = bcoin.mtx().addOutput(w, 50000).addOutput(w, 1000);
|
||||||
t1.addInput(dummyInput);
|
t1.addInput(dummyInput);
|
||||||
// balance: 51000
|
// balance: 51000
|
||||||
// w.sign(t1);
|
|
||||||
w.sign(t1, function(err) {
|
w.sign(t1, function(err) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
var t2 = bcoin.mtx().addInput(t1, 0) // 50000
|
var t2 = bcoin.mtx().addInput(t1, 0) // 50000
|
||||||
@ -200,14 +199,12 @@ describe('Wallet', function() {
|
|||||||
.addOutput(w, 24000);
|
.addOutput(w, 24000);
|
||||||
di = t2.inputs[0];
|
di = t2.inputs[0];
|
||||||
// balance: 49000
|
// balance: 49000
|
||||||
// w.sign(t2);
|
|
||||||
w.sign(t2, function(err) {
|
w.sign(t2, function(err) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
var t3 = bcoin.mtx().addInput(t1, 1) // 1000
|
var t3 = bcoin.mtx().addInput(t1, 1) // 1000
|
||||||
.addInput(t2, 0) // 24000
|
.addInput(t2, 0) // 24000
|
||||||
.addOutput(w, 23000);
|
.addOutput(w, 23000);
|
||||||
// balance: 47000
|
// balance: 47000
|
||||||
// w.sign(t3);
|
|
||||||
w.sign(t3, function(err) {
|
w.sign(t3, function(err) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
var t4 = bcoin.mtx().addInput(t2, 1) // 24000
|
var t4 = bcoin.mtx().addInput(t2, 1) // 24000
|
||||||
@ -215,19 +212,16 @@ describe('Wallet', function() {
|
|||||||
.addOutput(w, 11000)
|
.addOutput(w, 11000)
|
||||||
.addOutput(w, 11000);
|
.addOutput(w, 11000);
|
||||||
// balance: 22000
|
// balance: 22000
|
||||||
// w.sign(t4);
|
|
||||||
w.sign(t4, function(err) {
|
w.sign(t4, function(err) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
var f1 = bcoin.mtx().addInput(t4, 1) // 11000
|
var f1 = bcoin.mtx().addInput(t4, 1) // 11000
|
||||||
.addOutput(f, 10000);
|
.addOutput(f, 10000);
|
||||||
// balance: 11000
|
// balance: 11000
|
||||||
// w.sign(f1);
|
|
||||||
w.sign(f1, function(err) {
|
w.sign(f1, function(err) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
var fake = bcoin.mtx().addInput(t1, 1) // 1000 (already redeemed)
|
var fake = bcoin.mtx().addInput(t1, 1) // 1000 (already redeemed)
|
||||||
.addOutput(w, 500);
|
.addOutput(w, 500);
|
||||||
// Script inputs but do not sign
|
// Script inputs but do not sign
|
||||||
// w.scriptInputs(fake);
|
|
||||||
w.scriptInputs(fake, function(err) {
|
w.scriptInputs(fake, function(err) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
// Fake signature
|
// Fake signature
|
||||||
@ -300,13 +294,36 @@ describe('Wallet', function() {
|
|||||||
|
|
||||||
it('should cleanup spenders after double-spend', function(cb) {
|
it('should cleanup spenders after double-spend', function(cb) {
|
||||||
var t1 = bcoin.mtx().addOutput(dw, 5000);
|
var t1 = bcoin.mtx().addOutput(dw, 5000);
|
||||||
t1.addInput(di);
|
t1.addInput(di.coin);
|
||||||
walletdb.addTX(t1, function(err) {
|
dw.getHistory(function(err, txs) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
dw.getBalance(function(err, balance) {
|
assert.equal(txs.length, 5);
|
||||||
|
var total = txs.reduce(function(t, tx) {
|
||||||
|
return t + tx.getOutputValue();
|
||||||
|
}, 0);
|
||||||
|
assert.equal(total, 154000);
|
||||||
|
dw.sign(t1, function(err) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
assert.equal(balance.total, 11000);
|
dw.getBalance(function(err, balance) {
|
||||||
cb();
|
assert.ifError(err);
|
||||||
|
assert.equal(balance.total, 11000);
|
||||||
|
walletdb.addTX(t1, function(err) {
|
||||||
|
assert.ifError(err);
|
||||||
|
dw.getBalance(function(err, balance) {
|
||||||
|
assert.ifError(err);
|
||||||
|
assert.equal(balance.total, 6000);
|
||||||
|
dw.getHistory(function(err, txs) {
|
||||||
|
assert.ifError(err);
|
||||||
|
assert.equal(txs.length, 2);
|
||||||
|
var total = txs.reduce(function(t, tx) {
|
||||||
|
return t + tx.getOutputValue();
|
||||||
|
}, 0);
|
||||||
|
assert.equal(total, 56000);
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user