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) {
|
||||
var self = this;
|
||||
var iter;
|
||||
|
||||
if (!this.filter)
|
||||
return callback();
|
||||
|
||||
iter = this.db.iterator({
|
||||
this.db.iterate({
|
||||
gte: 'W',
|
||||
lte: 'W~',
|
||||
keys: true,
|
||||
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);
|
||||
|
||||
transform: function(key) {
|
||||
key = key.split('/')[1];
|
||||
|
||||
self.filter.add(key, 'hex');
|
||||
|
||||
next();
|
||||
});
|
||||
})();
|
||||
}
|
||||
}, callback);
|
||||
};
|
||||
|
||||
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) {
|
||||
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) {
|
||||
if (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) {
|
||||
var self = this;
|
||||
var hash = tx.hash('hex');
|
||||
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)
|
||||
return;
|
||||
|
||||
@ -341,14 +310,15 @@ TXDB.prototype._add = function add(tx, map, callback, force) {
|
||||
|
||||
// Ignore if we already have this tx.
|
||||
if (existing)
|
||||
return callback(null, true);
|
||||
return callback(null, true, map);
|
||||
|
||||
hash = tx.hash('hex');
|
||||
|
||||
batch = self.db.batch();
|
||||
|
||||
batch.put('t/' + hash, tx.toExtended());
|
||||
|
||||
if (tx.ts === 0) {
|
||||
assert(tx.ps > 0);
|
||||
batch.put('p/' + hash, DUMMY);
|
||||
batch.put('m/' + pad32(tx.ps) + '/' + hash, DUMMY);
|
||||
} else {
|
||||
@ -356,8 +326,9 @@ TXDB.prototype._add = function add(tx, map, callback, force) {
|
||||
batch.put('m/' + pad32(tx.ts) + '/' + hash, DUMMY);
|
||||
}
|
||||
|
||||
map.all.forEach(function(path) {
|
||||
var id = path.id + '/' + path.account;
|
||||
for (i = 0; i < map.all.length; i++) {
|
||||
path = map.all[i];
|
||||
id = path.id + '/' + path.account;
|
||||
batch.put('T/' + id + '/' + hash, DUMMY);
|
||||
if (tx.ts === 0) {
|
||||
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('M/' + id + '/' + pad32(tx.ts) + '/' + hash, DUMMY);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Consume unspent money or add orphans
|
||||
utils.forEachSerial(tx.inputs, function(input, next, i) {
|
||||
var key, address;
|
||||
var prevout = input.prevout;
|
||||
var key, address;
|
||||
|
||||
if (tx.isCoinbase())
|
||||
return next();
|
||||
@ -388,6 +359,8 @@ TXDB.prototype._add = function add(tx, map, callback, force) {
|
||||
|
||||
key = prevout.hash + '/' + prevout.index;
|
||||
|
||||
batch.put('s/' + key, tx.hash());
|
||||
|
||||
if (coin) {
|
||||
// Add TX to inputs and spend money
|
||||
input.coin = coin;
|
||||
@ -400,28 +373,28 @@ TXDB.prototype._add = function add(tx, map, callback, force) {
|
||||
|
||||
updated = true;
|
||||
|
||||
if (address) {
|
||||
map.table[address].forEach(function(path) {
|
||||
var id = path.id + '/' + path.account;
|
||||
batch.del('C/' + id + '/' + key);
|
||||
});
|
||||
paths = map.table[address];
|
||||
|
||||
for (j = 0; j < paths.length; j++) {
|
||||
path = paths[j];
|
||||
id = path.id + '/' + path.account;
|
||||
batch.del('C/' + id + '/' + key);
|
||||
}
|
||||
|
||||
batch.del('c/' + key);
|
||||
batch.put('s/' + key, tx.hash());
|
||||
|
||||
return next();
|
||||
}
|
||||
|
||||
input.coin = null;
|
||||
|
||||
self.isSpent(prevout.hash, prevout.index, function(err, spentBy) {
|
||||
self.isSpent(prevout.hash, prevout.index, function(err, spent) {
|
||||
if (err)
|
||||
return next(err);
|
||||
|
||||
// Are we double-spending?
|
||||
// Replace older txs with newer ones.
|
||||
if (spentBy) {
|
||||
if (spent) {
|
||||
return self.getTX(prevout.hash, function(err, prev) {
|
||||
if (err)
|
||||
return next(err);
|
||||
@ -434,19 +407,22 @@ TXDB.prototype._add = function add(tx, map, callback, force) {
|
||||
// Skip invalid transactions
|
||||
if (self.options.verify) {
|
||||
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)
|
||||
return next(err);
|
||||
|
||||
if (!result) {
|
||||
assert(tx.ts === 0, 'I\'m confused');
|
||||
return callback(null, false);
|
||||
}
|
||||
// Spender was not removed, the current
|
||||
// transaction is not elligible to be added.
|
||||
if (!rtx)
|
||||
return callback(null, false, map);
|
||||
|
||||
self.emit('conflict', rtx, rmap);
|
||||
|
||||
batch.clear();
|
||||
|
||||
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
|
||||
utils.forEachSerial(tx.outputs, function(output, next, i) {
|
||||
var address = output.getHash();
|
||||
var key, coin;
|
||||
var key = hash + '/' + i;
|
||||
var coin;
|
||||
|
||||
// Do not add unspents for outputs that aren't ours.
|
||||
if (!address || !map.table[address].length)
|
||||
return next();
|
||||
|
||||
key = hash + '/' + i;
|
||||
|
||||
if (output.script.isUnspendable())
|
||||
return next();
|
||||
|
||||
@ -518,11 +493,7 @@ TXDB.prototype._add = function add(tx, map, callback, force) {
|
||||
return next();
|
||||
}
|
||||
|
||||
self.lazyRemove(orphan.tx, function(err) {
|
||||
if (err)
|
||||
return next(err);
|
||||
return next();
|
||||
}, true);
|
||||
self.lazyRemove(orphan.tx, next, true);
|
||||
}, function(err) {
|
||||
if (err)
|
||||
return next(err);
|
||||
@ -538,11 +509,12 @@ TXDB.prototype._add = function add(tx, map, callback, force) {
|
||||
return next(err);
|
||||
|
||||
if (!orphans) {
|
||||
if (address) {
|
||||
map.table[address].forEach(function(path) {
|
||||
var id = path.id + '/' + path.account;
|
||||
batch.put('C/' + id + '/' + key, DUMMY);
|
||||
});
|
||||
paths = map.table[address];
|
||||
|
||||
for (j = 0; j < paths.length; j++) {
|
||||
path = paths[j];
|
||||
id = path.id + '/' + path.account;
|
||||
batch.put('C/' + id + '/' + key, DUMMY);
|
||||
}
|
||||
|
||||
batch.put('c/' + key, coin.toRaw());
|
||||
@ -573,7 +545,7 @@ TXDB.prototype._add = function add(tx, map, callback, force) {
|
||||
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].
|
||||
*/
|
||||
|
||||
TXDB.prototype._removeSpenders = function removeSpenders(hash, ref, callback) {
|
||||
TXDB.prototype._removeConflict = function _removeConflict(hash, ref, callback) {
|
||||
var self = this;
|
||||
|
||||
this.getTX(hash, function(err, tx) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
@ -602,32 +575,77 @@ TXDB.prototype._removeSpenders = function removeSpenders(hash, ref, callback) {
|
||||
if (!tx)
|
||||
return callback(new Error('Could not find spender.'));
|
||||
|
||||
if (tx.ts !== 0)
|
||||
return callback(null, false);
|
||||
if (tx.ts !== 0) {
|
||||
// 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)
|
||||
return callback(null, false);
|
||||
// If both are confirmed but replacement
|
||||
// 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) {
|
||||
self.isSpent(hash, i, function(err, spent) {
|
||||
if (err)
|
||||
return next(err);
|
||||
if (spent)
|
||||
return self._removeSpenders(spent, ref, next);
|
||||
next();
|
||||
});
|
||||
}, function(err) {
|
||||
// If both are unconfirmed but replacement
|
||||
// is older than spender, do nothing.
|
||||
if (ref.ps < tx.ps)
|
||||
return callback();
|
||||
}
|
||||
|
||||
self._removeRecursive(tx, function(err, result, map) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
return self.lazyRemove(tx, function(err) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
return callback(null, true);
|
||||
}, true);
|
||||
return callback(null, tx, map);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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) {
|
||||
var self = this;
|
||||
|
||||
utils.everySerial(tx.inputs, function(input, next) {
|
||||
self.isSpent(input.prevout.hash, input.prevout.index, function(err, spent) {
|
||||
if (err)
|
||||
@ -676,30 +695,33 @@ TXDB.prototype.isSpent = function isSpent(hash, index, callback) {
|
||||
|
||||
TXDB.prototype._confirm = function _confirm(tx, map, callback, force) {
|
||||
var self = this;
|
||||
var hash = tx.hash('hex');
|
||||
var batch;
|
||||
var hash, batch, unlock, i, path, id;
|
||||
|
||||
unlock = this._lock(_confirm, [tx, map, callback], force);
|
||||
|
||||
var unlock = this._lock(_confirm, [tx, map, callback], force);
|
||||
if (!unlock)
|
||||
return;
|
||||
|
||||
callback = utils.wrap(callback, unlock);
|
||||
|
||||
hash = tx.hash('hex');
|
||||
|
||||
this.getTX(hash, function(err, existing) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
// Haven't seen this tx before, add it.
|
||||
if (!existing)
|
||||
return callback(null, false);
|
||||
return callback(null, false, map);
|
||||
|
||||
// Existing tx is already confirmed. Ignore.
|
||||
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)
|
||||
return callback(null, true);
|
||||
return callback(null, true, map);
|
||||
|
||||
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.put('m/' + pad32(tx.ts) + '/' + hash, DUMMY);
|
||||
|
||||
map.all.forEach(function(path) {
|
||||
var id = path.id + '/' + path.account;
|
||||
for (i = 0; i < map.all.length; i++) {
|
||||
path = map.all[i];
|
||||
id = path.id + '/' + path.account;
|
||||
batch.del('P/' + id + '/' + hash);
|
||||
batch.put('H/' + id + '/' + pad32(tx.height) + '/' + hash, DUMMY);
|
||||
batch.del('M/' + id + '/' + pad32(existing.ps) + '/' + hash);
|
||||
batch.put('M/' + id + '/' + pad32(tx.ts) + '/' + hash, DUMMY);
|
||||
});
|
||||
}
|
||||
|
||||
utils.forEachSerial(tx.outputs, function(output, next, i) {
|
||||
var address = output.getHash();
|
||||
@ -758,7 +781,7 @@ TXDB.prototype._confirm = function _confirm(tx, map, callback, force) {
|
||||
self.emit('confirmed', 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) {
|
||||
var self = this;
|
||||
|
||||
if (Array.isArray(hash)) {
|
||||
return utils.forEachSerial(hash, function(hash, next) {
|
||||
self.remove(hash, next, force);
|
||||
}, callback);
|
||||
}
|
||||
|
||||
if (hash.hash)
|
||||
hash = hash.hash('hex');
|
||||
|
||||
@ -815,12 +832,6 @@ TXDB.prototype.remove = function remove(hash, callback, force) {
|
||||
TXDB.prototype.lazyRemove = function lazyRemove(tx, callback, force) {
|
||||
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) {
|
||||
if (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) {
|
||||
var self = this;
|
||||
var hash = tx.hash('hex');
|
||||
var batch;
|
||||
var unlock, hash, batch, i, j, path, id, key, paths, address, input, output;
|
||||
|
||||
unlock = this._lock(remove, [tx, map, callback], force);
|
||||
|
||||
var unlock = this._lock(remove, [tx, map, callback], force);
|
||||
if (!unlock)
|
||||
return;
|
||||
|
||||
callback = utils.wrap(callback, unlock);
|
||||
|
||||
hash = tx.hash('hex');
|
||||
|
||||
batch = this.db.batch();
|
||||
|
||||
batch.del('t/' + hash);
|
||||
@ -863,8 +876,9 @@ TXDB.prototype._remove = function remove(tx, map, callback, force) {
|
||||
batch.del('m/' + pad32(tx.ts) + '/' + hash);
|
||||
}
|
||||
|
||||
map.all.forEach(function(path) {
|
||||
var id = path.id + '/' + path.account;
|
||||
for (i = 0; i < map.all.length; i++) {
|
||||
path = map.all[i];
|
||||
id = path.id + '/' + path.account;
|
||||
batch.del('T/' + id + '/' + hash);
|
||||
if (tx.ts === 0) {
|
||||
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('M/' + id + '/' + pad32(tx.ts) + '/' + hash);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.fillHistory(tx, function(err) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
tx.inputs.forEach(function(input) {
|
||||
var key = input.prevout.hash + '/' + input.prevout.index;
|
||||
var address = input.getHash();
|
||||
for (i = 0; i < tx.inputs.length; i++) {
|
||||
input = tx.inputs[i];
|
||||
key = input.prevout.hash + '/' + input.prevout.index;
|
||||
address = input.getHash();
|
||||
|
||||
if (tx.isCoinbase())
|
||||
return;
|
||||
break;
|
||||
|
||||
if (!input.coin)
|
||||
return;
|
||||
continue;
|
||||
|
||||
if (!address || !map.table[address].length)
|
||||
return;
|
||||
continue;
|
||||
|
||||
if (address) {
|
||||
map.table[address].forEach(function(path) {
|
||||
var id = path.id + '/' + path.account;
|
||||
batch.put('C/' + id + '/' + key, DUMMY);
|
||||
});
|
||||
paths = map.table[address];
|
||||
|
||||
for (j = 0; j < paths.length; j++) {
|
||||
path = paths[j];
|
||||
id = path.id + '/' + path.account;
|
||||
batch.put('C/' + id + '/' + key, DUMMY);
|
||||
}
|
||||
|
||||
batch.put('c/' + key, input.coin.toRaw());
|
||||
batch.del('s/' + key);
|
||||
batch.del('o/' + key);
|
||||
});
|
||||
}
|
||||
|
||||
tx.outputs.forEach(function(output, i) {
|
||||
var key = hash + '/' + i;
|
||||
var address = output.getHash();
|
||||
for (i = 0; i < tx.outputs.length; i++) {
|
||||
output = tx.outputs[i];
|
||||
key = hash + '/' + i;
|
||||
address = output.getHash();
|
||||
|
||||
if (!address || !map.table[address].length)
|
||||
return;
|
||||
continue;
|
||||
|
||||
if (output.script.isUnspendable())
|
||||
return;
|
||||
continue;
|
||||
|
||||
if (address) {
|
||||
map.table[address].forEach(function(path) {
|
||||
var id = path.id + '/' + path.account;
|
||||
batch.del('C/' + id + '/' + key);
|
||||
});
|
||||
paths = map.table[address];
|
||||
|
||||
for (j = 0; j < paths.length; j++) {
|
||||
path = paths[j];
|
||||
id = path.id + '/' + path.account;
|
||||
batch.del('C/' + id + '/' + key);
|
||||
}
|
||||
|
||||
batch.del('c/' + key);
|
||||
});
|
||||
}
|
||||
|
||||
batch.write(function(err) {
|
||||
if (err)
|
||||
@ -930,7 +948,7 @@ TXDB.prototype._remove = function remove(tx, map, callback, force) {
|
||||
|
||||
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) {
|
||||
var self = this;
|
||||
|
||||
if (Array.isArray(hash)) {
|
||||
return utils.forEachSerial(hash, function(hash, next) {
|
||||
self.unconfirm(hash, next, force);
|
||||
}, callback);
|
||||
}
|
||||
|
||||
if (hash.hash)
|
||||
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) {
|
||||
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)
|
||||
return;
|
||||
|
||||
callback = utils.wrap(callback, unlock);
|
||||
|
||||
hash = tx.hash('hex');
|
||||
batch = this.db.batch();
|
||||
height = tx.height;
|
||||
ts = tx.ts;
|
||||
|
||||
batch = this.db.batch();
|
||||
|
||||
if (height !== -1)
|
||||
return callback(null, false);
|
||||
return callback(null, false, map);
|
||||
|
||||
tx.height = -1;
|
||||
tx.ps = utils.now();
|
||||
@ -1014,13 +1028,14 @@ TXDB.prototype._unconfirm = function unconfirm(tx, map, callback, force) {
|
||||
batch.del('m/' + pad32(ts) + '/' + hash);
|
||||
batch.put('m/' + pad32(tx.ps) + '/' + hash, DUMMY);
|
||||
|
||||
map.all.forEach(function(path) {
|
||||
var id = path.id + '/' + path.account;
|
||||
for (i = 0; i < map.all.length; i++) {
|
||||
path = map.all[i];
|
||||
id = path.id + '/' + path.account;
|
||||
batch.put('P/' + id + '/' + hash, DUMMY);
|
||||
batch.del('H/' + id + '/' + pad32(height) + '/' + hash);
|
||||
batch.del('M/' + id + '/' + pad32(ts) + '/' + hash);
|
||||
batch.put('M/' + id + '/' + pad32(tx.ps) + '/' + hash, DUMMY);
|
||||
});
|
||||
}
|
||||
|
||||
utils.forEachSerial(tx.outputs, function(output, next, i) {
|
||||
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);
|
||||
|
||||
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) {
|
||||
var self = this;
|
||||
var unlock;
|
||||
|
||||
if (typeof age === 'function') {
|
||||
force = callback;
|
||||
@ -1615,7 +1631,8 @@ TXDB.prototype.zap = function zap(id, age, callback, force) {
|
||||
id = null;
|
||||
}
|
||||
|
||||
var unlock = this._lock(zap, [id, age, callback], force);
|
||||
unlock = this._lock(zap, [id, age, callback], force);
|
||||
|
||||
if (!unlock)
|
||||
return;
|
||||
|
||||
|
||||
@ -2243,10 +2243,10 @@ utils.pad32 = function pad32(num) {
|
||||
*/
|
||||
|
||||
utils.wrap = function wrap(callback, unlock) {
|
||||
return function(err, result) {
|
||||
return function(err, res1, res2) {
|
||||
unlock();
|
||||
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]);
|
||||
|
||||
if (!unlock)
|
||||
return;
|
||||
|
||||
@ -235,6 +236,7 @@ Wallet.prototype.removeKey = function removeKey(account, key, callback) {
|
||||
}
|
||||
|
||||
unlock = this.locker.lock(removeKey, [account, key, callback]);
|
||||
|
||||
if (!unlock)
|
||||
return;
|
||||
|
||||
@ -339,6 +341,7 @@ Wallet.prototype.createAccount = function createAccount(options, callback, force
|
||||
var master, key, unlock;
|
||||
|
||||
unlock = this.locker.lock(createAccount, [options, callback], force);
|
||||
|
||||
if (!unlock)
|
||||
return;
|
||||
|
||||
@ -396,6 +399,7 @@ Wallet.prototype.getAccounts = function getAccounts(callback) {
|
||||
|
||||
Wallet.prototype.getAccount = function getAccount(account, callback, force) {
|
||||
var unlock = this.locker.lock(getAccount, [account, callback], force);
|
||||
|
||||
if (!unlock)
|
||||
return;
|
||||
|
||||
@ -454,6 +458,7 @@ Wallet.prototype.createAddress = function createAddress(account, change, callbac
|
||||
}
|
||||
|
||||
unlock = this.locker.lock(createAddress, [account, change, callback]);
|
||||
|
||||
if (!unlock)
|
||||
return;
|
||||
|
||||
@ -790,6 +795,7 @@ Wallet.prototype.syncOutputDepth = function syncOutputDepth(tx, callback) {
|
||||
var i, path, unlock;
|
||||
|
||||
unlock = this.locker.lock(syncOutputDepth, [tx, callback]);
|
||||
|
||||
if (!unlock)
|
||||
return;
|
||||
|
||||
@ -911,6 +917,7 @@ Wallet.prototype.scan = function scan(getByAddress, callback) {
|
||||
var unlock;
|
||||
|
||||
unlock = this.locker.lock(scan, [getByAddress, callback]);
|
||||
|
||||
if (!unlock)
|
||||
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) {
|
||||
self.emit('confirmed', tx, map);
|
||||
map.all.forEach(function(path) {
|
||||
|
||||
@ -192,7 +192,6 @@ describe('Wallet', function() {
|
||||
var t1 = bcoin.mtx().addOutput(w, 50000).addOutput(w, 1000);
|
||||
t1.addInput(dummyInput);
|
||||
// balance: 51000
|
||||
// w.sign(t1);
|
||||
w.sign(t1, function(err) {
|
||||
assert.ifError(err);
|
||||
var t2 = bcoin.mtx().addInput(t1, 0) // 50000
|
||||
@ -200,14 +199,12 @@ describe('Wallet', function() {
|
||||
.addOutput(w, 24000);
|
||||
di = t2.inputs[0];
|
||||
// balance: 49000
|
||||
// w.sign(t2);
|
||||
w.sign(t2, function(err) {
|
||||
assert.ifError(err);
|
||||
var t3 = bcoin.mtx().addInput(t1, 1) // 1000
|
||||
.addInput(t2, 0) // 24000
|
||||
.addOutput(w, 23000);
|
||||
// balance: 47000
|
||||
// w.sign(t3);
|
||||
w.sign(t3, function(err) {
|
||||
assert.ifError(err);
|
||||
var t4 = bcoin.mtx().addInput(t2, 1) // 24000
|
||||
@ -215,19 +212,16 @@ describe('Wallet', function() {
|
||||
.addOutput(w, 11000)
|
||||
.addOutput(w, 11000);
|
||||
// balance: 22000
|
||||
// w.sign(t4);
|
||||
w.sign(t4, function(err) {
|
||||
assert.ifError(err);
|
||||
var f1 = bcoin.mtx().addInput(t4, 1) // 11000
|
||||
.addOutput(f, 10000);
|
||||
// balance: 11000
|
||||
// w.sign(f1);
|
||||
w.sign(f1, function(err) {
|
||||
assert.ifError(err);
|
||||
var fake = bcoin.mtx().addInput(t1, 1) // 1000 (already redeemed)
|
||||
.addOutput(w, 500);
|
||||
// Script inputs but do not sign
|
||||
// w.scriptInputs(fake);
|
||||
w.scriptInputs(fake, function(err) {
|
||||
assert.ifError(err);
|
||||
// Fake signature
|
||||
@ -300,13 +294,36 @@ describe('Wallet', function() {
|
||||
|
||||
it('should cleanup spenders after double-spend', function(cb) {
|
||||
var t1 = bcoin.mtx().addOutput(dw, 5000);
|
||||
t1.addInput(di);
|
||||
walletdb.addTX(t1, function(err) {
|
||||
t1.addInput(di.coin);
|
||||
dw.getHistory(function(err, txs) {
|
||||
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.equal(balance.total, 11000);
|
||||
cb();
|
||||
dw.getBalance(function(err, balance) {
|
||||
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