txdb: refactor batches.
This commit is contained in:
parent
400680e208
commit
9c94fae2a8
@ -216,6 +216,7 @@ TXDB.prototype.mapAddresses = function mapAddresses(address, callback) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
TXDB.prototype._addOrphan = function _addOrphan(key, hash, index, callback) {
|
TXDB.prototype._addOrphan = function _addOrphan(key, hash, index, callback) {
|
||||||
|
var self = this;
|
||||||
var p;
|
var p;
|
||||||
|
|
||||||
this.db.get('o/' + key, function(err, buf) {
|
this.db.get('o/' + key, function(err, buf) {
|
||||||
@ -229,7 +230,9 @@ TXDB.prototype._addOrphan = function _addOrphan(key, hash, index, callback) {
|
|||||||
|
|
||||||
bcoin.outpoint(hash, index).toRaw(p);
|
bcoin.outpoint(hash, index).toRaw(p);
|
||||||
|
|
||||||
return callback(null, p.render());
|
self.batch().put('o/' + key, p.render());
|
||||||
|
|
||||||
|
return callback();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -396,6 +399,46 @@ TXDB.prototype.removeBlock = function removeBlock(block, callback, force) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
TXDB.prototype.start = function start() {
|
||||||
|
assert(!this.current);
|
||||||
|
this.current = this.db.batch();
|
||||||
|
return this.current;
|
||||||
|
};
|
||||||
|
|
||||||
|
TXDB.prototype.put = function put(key, value) {
|
||||||
|
assert(this.current);
|
||||||
|
this.current.put(key, value);
|
||||||
|
};
|
||||||
|
|
||||||
|
TXDB.prototype.del = function del(key, value) {
|
||||||
|
assert(this.current);
|
||||||
|
this.current.del(key);
|
||||||
|
};
|
||||||
|
|
||||||
|
TXDB.prototype.batch = function batch() {
|
||||||
|
assert(this.current);
|
||||||
|
return this.current;
|
||||||
|
};
|
||||||
|
|
||||||
|
TXDB.prototype.drop = function drop() {
|
||||||
|
assert(this.current);
|
||||||
|
this.current.clear();
|
||||||
|
this.current = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
TXDB.prototype.commit = function commit(callback) {
|
||||||
|
var self = this;
|
||||||
|
assert(this.current);
|
||||||
|
this.current.write(function(err) {
|
||||||
|
if (err) {
|
||||||
|
self.current = null;
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
self.current = null;
|
||||||
|
return callback();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a transaction to the database, map addresses
|
* Add a transaction to the database, map addresses
|
||||||
* to wallet IDs, potentially store orphans, resolve
|
* to wallet IDs, potentially store orphans, resolve
|
||||||
@ -406,6 +449,12 @@ TXDB.prototype.removeBlock = function removeBlock(block, callback, force) {
|
|||||||
|
|
||||||
TXDB.prototype.add = function add(tx, callback, force) {
|
TXDB.prototype.add = function add(tx, callback, force) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
var unlock = this._lock(add, [tx, callback], force);
|
||||||
|
|
||||||
|
if (!unlock)
|
||||||
|
return;
|
||||||
|
|
||||||
|
callback = utils.wrap(callback, unlock);
|
||||||
|
|
||||||
return this.getInfo(tx, function(err, info) {
|
return this.getInfo(tx, function(err, info) {
|
||||||
if (err)
|
if (err)
|
||||||
@ -420,22 +469,147 @@ TXDB.prototype.add = function add(tx, callback, force) {
|
|||||||
|
|
||||||
self.logger.debug(info.paths);
|
self.logger.debug(info.paths);
|
||||||
|
|
||||||
return self._add(tx, info, callback, force);
|
return self._add(tx, info, callback);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
TXDB.prototype._add = function add(tx, info, callback, force) {
|
TXDB.prototype._verify = function _verify(tx, info, callback) {
|
||||||
|
var self = this;
|
||||||
|
utils.forEachSerial(tx.inputs, function(input, next, i) {
|
||||||
|
var prevout = input.prevout;
|
||||||
|
var address, paths;
|
||||||
|
|
||||||
|
if (tx.isCoinbase())
|
||||||
|
return next();
|
||||||
|
|
||||||
|
address = input.getHash('hex');
|
||||||
|
paths = info.getPaths(address);
|
||||||
|
|
||||||
|
// Only bother if this input is ours.
|
||||||
|
if (!paths)
|
||||||
|
return next();
|
||||||
|
|
||||||
|
self.getCoin(prevout.hash, prevout.index, function(err, coin) {
|
||||||
|
if (err)
|
||||||
|
return next(err);
|
||||||
|
|
||||||
|
if (coin) {
|
||||||
|
// Add TX to inputs and spend money
|
||||||
|
input.coin = coin;
|
||||||
|
|
||||||
|
// Skip invalid transactions
|
||||||
|
if (self.options.verify) {
|
||||||
|
if (!tx.verifyInput(i))
|
||||||
|
return callback(null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
input.coin = null;
|
||||||
|
|
||||||
|
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 (!spent)
|
||||||
|
return next();
|
||||||
|
|
||||||
|
self.getTX(prevout.hash, function(err, prev) {
|
||||||
|
if (err)
|
||||||
|
return next(err);
|
||||||
|
|
||||||
|
if (!prev)
|
||||||
|
return callback(new Error('Could not find double-spent coin.'));
|
||||||
|
|
||||||
|
input.coin = bcoin.coin.fromTX(prev, prevout.index);
|
||||||
|
|
||||||
|
// Skip invalid transactions
|
||||||
|
if (self.options.verify) {
|
||||||
|
if (!tx.verifyInput(i))
|
||||||
|
return callback(null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
self._removeConflict(spent.hash, tx, function(err, rtx, rinfo) {
|
||||||
|
if (err)
|
||||||
|
return next(err);
|
||||||
|
|
||||||
|
// Spender was not removed, the current
|
||||||
|
// transaction is not elligible to be added.
|
||||||
|
if (!rtx)
|
||||||
|
return callback(null, false);
|
||||||
|
|
||||||
|
self.emit('conflict', rtx, rinfo);
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, function(err) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
return callback(null, true);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
TXDB.prototype._resolveOrphans = function _resolveOrphans(tx, i, callback) {
|
||||||
|
var self = this;
|
||||||
|
var batch = this.batch();
|
||||||
|
var hash = tx.hash('hex');
|
||||||
|
var output = tx.outputs[i];
|
||||||
|
var key = hash + '/' + i;
|
||||||
|
var coin;
|
||||||
|
|
||||||
|
this._getOrphans(key, function(err, orphans) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
|
||||||
|
if (!orphans)
|
||||||
|
return callback(null, false);
|
||||||
|
|
||||||
|
batch.del('o/' + key);
|
||||||
|
|
||||||
|
coin = bcoin.coin.fromTX(tx, i);
|
||||||
|
|
||||||
|
// Add input to orphan
|
||||||
|
utils.forEachSerial(orphans, function(pair, next) {
|
||||||
|
var input = pair[0];
|
||||||
|
var orphan = pair[1];
|
||||||
|
|
||||||
|
// Probably removed by some other means.
|
||||||
|
if (!orphan)
|
||||||
|
return next();
|
||||||
|
|
||||||
|
orphan.inputs[input.index].coin = coin;
|
||||||
|
|
||||||
|
assert(orphan.inputs[input.index].prevout.hash === hash);
|
||||||
|
assert(orphan.inputs[input.index].prevout.index === i);
|
||||||
|
|
||||||
|
// Verify that input script is correct, if not - add
|
||||||
|
// output to unspent and remove orphan from storage
|
||||||
|
if (!self.options.verify || orphan.verifyInput(input.index)) {
|
||||||
|
batch.put('d/' + input.hash + '/' + pad32(input.index), coin.toRaw());
|
||||||
|
return callback(null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
self._lazyRemove(orphan, next);
|
||||||
|
}, function(err) {
|
||||||
|
if (err)
|
||||||
|
return next(err);
|
||||||
|
|
||||||
|
return callback(null, false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
TXDB.prototype._add = function add(tx, info, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var updated = false;
|
var updated = false;
|
||||||
var batch, hash, i, j, unlock, path, paths, id;
|
var batch, hash, i, j, unlock, path, paths, id;
|
||||||
|
|
||||||
unlock = this._lock(add, [tx, info, callback], force);
|
|
||||||
|
|
||||||
if (!unlock)
|
|
||||||
return;
|
|
||||||
|
|
||||||
callback = utils.wrap(callback, unlock);
|
|
||||||
|
|
||||||
if (tx.mutable)
|
if (tx.mutable)
|
||||||
tx = tx.toTX();
|
tx = tx.toTX();
|
||||||
|
|
||||||
@ -448,248 +622,147 @@ TXDB.prototype._add = function add(tx, info, callback, force) {
|
|||||||
if (existing)
|
if (existing)
|
||||||
return callback(null, true, info);
|
return callback(null, true, info);
|
||||||
|
|
||||||
hash = tx.hash('hex');
|
self._verify(tx, info, function(err, result) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
|
||||||
batch = self.db.batch();
|
if (!result)
|
||||||
|
return callback(null, result, info);
|
||||||
|
|
||||||
batch.put('t/' + hash, tx.toExtended());
|
hash = tx.hash('hex');
|
||||||
|
|
||||||
if (tx.ts === 0)
|
self.start();
|
||||||
batch.put('p/' + hash, DUMMY);
|
batch = self.batch();
|
||||||
else
|
batch.put('t/' + hash, tx.toExtended());
|
||||||
batch.put('h/' + pad32(tx.height) + '/' + hash, DUMMY);
|
|
||||||
|
|
||||||
batch.put('m/' + pad32(tx.ps) + '/' + hash, DUMMY);
|
|
||||||
|
|
||||||
for (i = 0; i < info.keys.length; i++) {
|
|
||||||
id = info.keys[i];
|
|
||||||
batch.put('T/' + id + '/' + hash, DUMMY);
|
|
||||||
if (tx.ts === 0)
|
if (tx.ts === 0)
|
||||||
batch.put('P/' + id + '/' + hash, DUMMY);
|
batch.put('p/' + hash, DUMMY);
|
||||||
else
|
else
|
||||||
batch.put('H/' + id + '/' + pad32(tx.height) + '/' + hash, DUMMY);
|
batch.put('h/' + pad32(tx.height) + '/' + hash, DUMMY);
|
||||||
batch.put('M/' + id + '/' + pad32(tx.ps) + '/' + hash, DUMMY);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consume unspent money or add orphans
|
batch.put('m/' + pad32(tx.ps) + '/' + hash, DUMMY);
|
||||||
utils.forEachSerial(tx.inputs, function(input, next, i) {
|
|
||||||
var prevout = input.prevout;
|
|
||||||
var key, address;
|
|
||||||
|
|
||||||
if (tx.isCoinbase())
|
for (i = 0; i < info.keys.length; i++) {
|
||||||
return next();
|
id = info.keys[i];
|
||||||
|
batch.put('T/' + id + '/' + hash, DUMMY);
|
||||||
|
if (tx.ts === 0)
|
||||||
|
batch.put('P/' + id + '/' + hash, DUMMY);
|
||||||
|
else
|
||||||
|
batch.put('H/' + id + '/' + pad32(tx.height) + '/' + hash, DUMMY);
|
||||||
|
batch.put('M/' + id + '/' + pad32(tx.ps) + '/' + hash, DUMMY);
|
||||||
|
}
|
||||||
|
|
||||||
address = input.getHash('hex');
|
// Consume unspent money or add orphans
|
||||||
paths = info.getPaths(address);
|
utils.forEachSerial(tx.inputs, function(input, next, i) {
|
||||||
|
var prevout = input.prevout;
|
||||||
|
var key, address;
|
||||||
|
|
||||||
// Only bother if this input is ours.
|
if (tx.isCoinbase())
|
||||||
if (!paths)
|
return next();
|
||||||
return next();
|
|
||||||
|
|
||||||
self.getCoin(prevout.hash, prevout.index, function(err, coin) {
|
address = input.getHash('hex');
|
||||||
if (err)
|
paths = info.getPaths(address);
|
||||||
return next(err);
|
|
||||||
|
// Only bother if this input is ours.
|
||||||
|
if (!paths)
|
||||||
|
return next();
|
||||||
|
|
||||||
key = prevout.hash + '/' + prevout.index;
|
key = prevout.hash + '/' + prevout.index;
|
||||||
|
|
||||||
// s/[outpoint-key] -> [spender-hash]|[spender-input-index]
|
// s/[outpoint-key] -> [spender-hash]|[spender-input-index]
|
||||||
batch.put('s/' + key, bcoin.outpoint.fromTX(tx, i).toRaw());
|
batch.put('s/' + key, bcoin.outpoint.fromTX(tx, i).toRaw());
|
||||||
|
|
||||||
if (coin) {
|
if (!input.coin) {
|
||||||
// Add TX to inputs and spend money
|
// Add orphan, if no parent transaction is yet known
|
||||||
input.coin = coin;
|
return self._addOrphan(key, hash, i, next);
|
||||||
|
|
||||||
// Skip invalid transactions
|
|
||||||
if (self.options.verify) {
|
|
||||||
if (!tx.verifyInput(i))
|
|
||||||
return callback(null, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
updated = true;
|
|
||||||
|
|
||||||
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('d/' + tx.hash('hex') + '/' + pad32(i), coin.toRaw());
|
|
||||||
|
|
||||||
self.coinCache.remove(key);
|
|
||||||
|
|
||||||
return next();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input.coin = null;
|
updated = true;
|
||||||
|
|
||||||
self.isSpent(prevout.hash, prevout.index, function(err, spent) {
|
for (j = 0; j < paths.length; j++) {
|
||||||
if (err)
|
path = paths[j];
|
||||||
return next(err);
|
id = path.id + '/' + path.account;
|
||||||
|
batch.del('C/' + id + '/' + key);
|
||||||
|
}
|
||||||
|
|
||||||
// Are we double-spending?
|
batch.del('c/' + key);
|
||||||
// Replace older txs with newer ones.
|
batch.put('d/' + hash + '/' + pad32(i), input.coin.toRaw());
|
||||||
if (spent) {
|
|
||||||
return self.getTX(prevout.hash, function(err, prev) {
|
|
||||||
if (err)
|
|
||||||
return next(err);
|
|
||||||
|
|
||||||
if (!prev)
|
self.coinCache.remove(key);
|
||||||
return callback(new Error('Could not find double-spent coin.'));
|
|
||||||
|
|
||||||
input.coin = bcoin.coin.fromTX(prev, prevout.index);
|
next();
|
||||||
|
|
||||||
// Skip invalid transactions
|
|
||||||
if (self.options.verify) {
|
|
||||||
if (!tx.verifyInput(i))
|
|
||||||
return callback(null, false, info);
|
|
||||||
}
|
|
||||||
|
|
||||||
return self._removeConflict(spent, tx, function(err, rtx, rinfo) {
|
|
||||||
if (err)
|
|
||||||
return next(err);
|
|
||||||
|
|
||||||
// Spender was not removed, the current
|
|
||||||
// transaction is not elligible to be added.
|
|
||||||
if (!rtx)
|
|
||||||
return callback(null, false, info);
|
|
||||||
|
|
||||||
self.emit('conflict', rtx, rinfo);
|
|
||||||
|
|
||||||
batch.clear();
|
|
||||||
|
|
||||||
self._add(tx, info, callback, true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add orphan, if no parent transaction is yet known
|
|
||||||
self._addOrphan(key, hash, i, function(err, orphans) {
|
|
||||||
if (err)
|
|
||||||
return next(err);
|
|
||||||
|
|
||||||
batch.put('o/' + key, orphans);
|
|
||||||
|
|
||||||
return next();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, function(err) {
|
|
||||||
if (err)
|
|
||||||
return callback(err);
|
|
||||||
|
|
||||||
// Add unspent outputs or resolve orphans
|
|
||||||
utils.forEachSerial(tx.outputs, function(output, next, i) {
|
|
||||||
var address = output.getHash('hex');
|
|
||||||
var key = hash + '/' + i;
|
|
||||||
var coin;
|
|
||||||
|
|
||||||
if (output.script.isUnspendable())
|
|
||||||
return next();
|
|
||||||
|
|
||||||
paths = info.getPaths(address);
|
|
||||||
|
|
||||||
// Do not add unspents for outputs that aren't ours.
|
|
||||||
if (!paths)
|
|
||||||
return next();
|
|
||||||
|
|
||||||
coin = bcoin.coin.fromTX(tx, i);
|
|
||||||
|
|
||||||
self._getOrphans(key, function(err, orphans) {
|
|
||||||
var some = false;
|
|
||||||
|
|
||||||
if (err)
|
|
||||||
return callback(err);
|
|
||||||
|
|
||||||
if (!orphans)
|
|
||||||
return finish();
|
|
||||||
|
|
||||||
// Add input to orphan
|
|
||||||
utils.forEachSerial(orphans, function(pair, next) {
|
|
||||||
if (some)
|
|
||||||
return next();
|
|
||||||
|
|
||||||
var input = pair[0];
|
|
||||||
var orphan = pair[1];
|
|
||||||
|
|
||||||
// Probably removed by some other means.
|
|
||||||
if (!orphan)
|
|
||||||
return next();
|
|
||||||
|
|
||||||
orphan.inputs[input.index].coin = coin;
|
|
||||||
|
|
||||||
assert(orphan.inputs[input.index].prevout.hash === hash);
|
|
||||||
assert(orphan.inputs[input.index].prevout.index === i);
|
|
||||||
|
|
||||||
// Verify that input script is correct, if not - add
|
|
||||||
// output to unspent and remove orphan from storage
|
|
||||||
if (!self.options.verify || orphan.verifyInput(input.index)) {
|
|
||||||
some = true;
|
|
||||||
batch.put('d/' + input.hash + '/' + pad32(input.index), coin.toRaw());
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.lazyRemove(orphan, next, true);
|
|
||||||
}, function(err) {
|
|
||||||
if (err)
|
|
||||||
return next(err);
|
|
||||||
|
|
||||||
if (!some)
|
|
||||||
orphans = null;
|
|
||||||
|
|
||||||
self.db.del('o/' + key, finish);
|
|
||||||
});
|
|
||||||
|
|
||||||
function finish(err) {
|
|
||||||
if (err)
|
|
||||||
return next(err);
|
|
||||||
|
|
||||||
if (!orphans) {
|
|
||||||
for (j = 0; j < paths.length; j++) {
|
|
||||||
path = paths[j];
|
|
||||||
id = path.id + '/' + path.account;
|
|
||||||
batch.put('C/' + id + '/' + key, DUMMY);
|
|
||||||
}
|
|
||||||
|
|
||||||
coin = coin.toRaw();
|
|
||||||
|
|
||||||
batch.put('c/' + key, coin);
|
|
||||||
|
|
||||||
self.coinCache.set(key, coin);
|
|
||||||
|
|
||||||
updated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
batch.write(function(err) {
|
// Add unspent outputs or resolve orphans
|
||||||
if (err)
|
utils.forEachSerial(tx.outputs, function(output, next, i) {
|
||||||
return callback(err);
|
var address = output.getHash('hex');
|
||||||
|
var key = hash + '/' + i;
|
||||||
|
var coin;
|
||||||
|
|
||||||
self.walletdb.handleTX(tx, info, function(err) {
|
if (output.script.isUnspendable())
|
||||||
|
return next();
|
||||||
|
|
||||||
|
paths = info.getPaths(address);
|
||||||
|
|
||||||
|
// Do not add unspents for outputs that aren't ours.
|
||||||
|
if (!paths)
|
||||||
|
return next();
|
||||||
|
|
||||||
|
self._resolveOrphans(tx, i, function(err, orphans) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
self.emit('tx', tx, info);
|
if (orphans)
|
||||||
|
return next();
|
||||||
|
|
||||||
if (updated) {
|
coin = bcoin.coin.fromTX(tx, i);
|
||||||
if (tx.ts !== 0)
|
|
||||||
self.emit('confirmed', tx, info);
|
|
||||||
|
|
||||||
self.emit('updated', tx, info);
|
for (j = 0; j < paths.length; j++) {
|
||||||
|
path = paths[j];
|
||||||
|
id = path.id + '/' + path.account;
|
||||||
|
batch.put('C/' + id + '/' + key, DUMMY);
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback(null, true, info);
|
coin = coin.toRaw();
|
||||||
|
|
||||||
|
batch.put('c/' + key, coin);
|
||||||
|
|
||||||
|
self.coinCache.set(key, coin);
|
||||||
|
|
||||||
|
updated = true;
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}, function(err) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
|
||||||
|
self.commit(function(err) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
|
||||||
|
self.walletdb.handleTX(tx, info, function(err) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
|
||||||
|
self.emit('tx', tx, info);
|
||||||
|
|
||||||
|
if (updated) {
|
||||||
|
if (tx.ts !== 0)
|
||||||
|
self.emit('confirmed', tx, info);
|
||||||
|
|
||||||
|
self.emit('updated', tx, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
return callback(null, true, info);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, true);
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -763,7 +836,7 @@ TXDB.prototype._removeRecursive = function _removeRecursive(tx, callback) {
|
|||||||
|
|
||||||
// Remove all of the spender's spenders first.
|
// Remove all of the spender's spenders first.
|
||||||
if (spent) {
|
if (spent) {
|
||||||
return self.getTX(spent, function(err, tx) {
|
return self.getTX(spent.hash, function(err, tx) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
@ -780,8 +853,19 @@ TXDB.prototype._removeRecursive = function _removeRecursive(tx, callback) {
|
|||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
|
self.start();
|
||||||
|
|
||||||
// Remove the spender.
|
// Remove the spender.
|
||||||
return self.lazyRemove(tx, callback, true);
|
self._lazyRemove(tx, function(err, res1, res2) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
|
||||||
|
self.commit(function(err) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
callback(null, res1, res2);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -817,8 +901,8 @@ TXDB.prototype.isDoubleSpend = function isDoubleSpend(tx, callback) {
|
|||||||
|
|
||||||
TXDB.prototype.isSpent = function isSpent(hash, index, callback) {
|
TXDB.prototype.isSpent = function isSpent(hash, index, callback) {
|
||||||
var key = 's/' + hash + '/' + index;
|
var key = 's/' + hash + '/' + index;
|
||||||
return this.db.fetch(key, function(hash) {
|
return this.db.fetch(key, function(data) {
|
||||||
return hash.toString('hex', 0, 32);
|
return bcoin.outpoint.fromRaw(data);
|
||||||
}, callback);
|
}, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -832,17 +916,10 @@ TXDB.prototype.isSpent = function isSpent(hash, index, callback) {
|
|||||||
* transaction was confirmed, or should be ignored.
|
* transaction was confirmed, or should be ignored.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
TXDB.prototype._confirm = function _confirm(tx, info, callback, force) {
|
TXDB.prototype._confirm = function _confirm(tx, info, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var hash, batch, unlock, i, id;
|
var hash, batch, unlock, i, id;
|
||||||
|
|
||||||
unlock = this._lock(_confirm, [tx, info, callback], force);
|
|
||||||
|
|
||||||
if (!unlock)
|
|
||||||
return;
|
|
||||||
|
|
||||||
callback = utils.wrap(callback, unlock);
|
|
||||||
|
|
||||||
hash = tx.hash('hex');
|
hash = tx.hash('hex');
|
||||||
|
|
||||||
this.getTX(hash, function(err, existing) {
|
this.getTX(hash, function(err, existing) {
|
||||||
@ -862,12 +939,12 @@ TXDB.prototype._confirm = function _confirm(tx, info, callback, force) {
|
|||||||
if (tx.ts === 0)
|
if (tx.ts === 0)
|
||||||
return callback(null, true, info);
|
return callback(null, true, info);
|
||||||
|
|
||||||
batch = self.db.batch();
|
|
||||||
|
|
||||||
// Tricky - update the tx and coin in storage,
|
// Tricky - update the tx and coin in storage,
|
||||||
// and remove pending flag to mark as confirmed.
|
// and remove pending flag to mark as confirmed.
|
||||||
assert(tx.height >= 0);
|
assert(tx.height >= 0);
|
||||||
|
|
||||||
|
self.start();
|
||||||
|
batch = self.batch();
|
||||||
batch.put('t/' + hash, tx.toExtended());
|
batch.put('t/' + hash, tx.toExtended());
|
||||||
|
|
||||||
batch.del('p/' + hash);
|
batch.del('p/' + hash);
|
||||||
@ -909,19 +986,13 @@ TXDB.prototype._confirm = function _confirm(tx, info, callback, force) {
|
|||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
batch.write(function(err) {
|
self.emit('confirmed', tx, info);
|
||||||
|
self.emit('tx', tx, info);
|
||||||
|
|
||||||
|
self.commit(function(err) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
return callback(null, true, info);
|
||||||
self.walletdb.syncOutputs(tx, info, function(err) {
|
|
||||||
if (err)
|
|
||||||
return callback(err);
|
|
||||||
|
|
||||||
self.emit('confirmed', tx, info);
|
|
||||||
self.emit('tx', tx, info);
|
|
||||||
|
|
||||||
return callback(null, true, info);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -955,7 +1026,19 @@ TXDB.prototype.remove = function remove(hash, callback, force) {
|
|||||||
if (!info)
|
if (!info)
|
||||||
return callback(null, false);
|
return callback(null, false);
|
||||||
|
|
||||||
return self._remove(tx, info, callback, force);
|
self.start();
|
||||||
|
|
||||||
|
return self._remove(tx, info, function(err, res1, res2) {
|
||||||
|
if (err) {
|
||||||
|
self.drop();
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
self.commit(function(err) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
callback(null, res1, res2);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -968,17 +1051,16 @@ TXDB.prototype.remove = function remove(hash, callback, force) {
|
|||||||
* @param {Function} callback - Returns [Error].
|
* @param {Function} callback - Returns [Error].
|
||||||
*/
|
*/
|
||||||
|
|
||||||
TXDB.prototype.lazyRemove = function lazyRemove(tx, callback, force) {
|
TXDB.prototype._lazyRemove = function lazyRemove(tx, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
this.getInfo(tx, function(err, info) {
|
||||||
return this.getInfo(tx, function(err, info) {
|
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
if (!info)
|
if (!info)
|
||||||
return callback(null, false);
|
return callback(null, false);
|
||||||
|
|
||||||
return self._remove(tx, info, callback, force);
|
return self._remove(tx, info, callback);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -990,21 +1072,14 @@ TXDB.prototype.lazyRemove = function lazyRemove(tx, callback, force) {
|
|||||||
* @param {Function} callback - Returns [Error].
|
* @param {Function} callback - Returns [Error].
|
||||||
*/
|
*/
|
||||||
|
|
||||||
TXDB.prototype._remove = function remove(tx, info, callback, force) {
|
TXDB.prototype._remove = function remove(tx, info, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var unlock, hash, batch, i, j, path, id;
|
var unlock, hash, batch, i, j, path, id;
|
||||||
var key, paths, address, input, output, coin;
|
var key, paths, address, input, output, coin;
|
||||||
|
|
||||||
unlock = this._lock(remove, [tx, info, callback], force);
|
|
||||||
|
|
||||||
if (!unlock)
|
|
||||||
return;
|
|
||||||
|
|
||||||
callback = utils.wrap(callback, unlock);
|
|
||||||
|
|
||||||
hash = tx.hash('hex');
|
hash = tx.hash('hex');
|
||||||
|
|
||||||
batch = this.db.batch();
|
batch = this.batch();
|
||||||
|
|
||||||
batch.del('t/' + hash);
|
batch.del('t/' + hash);
|
||||||
|
|
||||||
@ -1085,14 +1160,9 @@ TXDB.prototype._remove = function remove(tx, info, callback, force) {
|
|||||||
self.coinCache.remove(key);
|
self.coinCache.remove(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
batch.write(function(err) {
|
self.emit('remove tx', tx, info);
|
||||||
if (err)
|
|
||||||
return callback(err);
|
|
||||||
|
|
||||||
self.emit('remove tx', tx, info);
|
return callback(null, true, info);
|
||||||
|
|
||||||
return callback(null, true, info);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1104,12 +1174,16 @@ TXDB.prototype._remove = function remove(tx, info, callback, force) {
|
|||||||
|
|
||||||
TXDB.prototype.unconfirm = function unconfirm(hash, callback, force) {
|
TXDB.prototype.unconfirm = function unconfirm(hash, callback, force) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
var unlock = this._lock(unconfirm, [tx, callback], force);
|
||||||
|
|
||||||
|
if (!unlock)
|
||||||
|
return;
|
||||||
|
|
||||||
|
callback = utils.wrap(callback, unlock);
|
||||||
|
|
||||||
if (hash.hash)
|
if (hash.hash)
|
||||||
hash = hash.hash('hex');
|
hash = hash.hash('hex');
|
||||||
|
|
||||||
callback = utils.ensure(callback);
|
|
||||||
|
|
||||||
this.getTX(hash, function(err, tx) {
|
this.getTX(hash, function(err, tx) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
@ -1126,7 +1200,19 @@ TXDB.prototype.unconfirm = function unconfirm(hash, callback, force) {
|
|||||||
if (!info)
|
if (!info)
|
||||||
return callback(null, false);
|
return callback(null, false);
|
||||||
|
|
||||||
return self._unconfirm(tx, info, callback, force);
|
self.start();
|
||||||
|
|
||||||
|
return self._unconfirm(tx, info, function(err, res1, res2) {
|
||||||
|
if (err) {
|
||||||
|
self.drop();
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
self.commit(function(err) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
callback(null, res1, res2);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -1142,13 +1228,7 @@ TXDB.prototype._unconfirm = function unconfirm(tx, info, callback, force) {
|
|||||||
var self = this;
|
var self = this;
|
||||||
var batch, unlock, hash, height, i, id;
|
var batch, unlock, hash, height, i, id;
|
||||||
|
|
||||||
unlock = this._lock(unconfirm, [tx, info, callback], force);
|
batch = this.batch();
|
||||||
|
|
||||||
if (!unlock)
|
|
||||||
return;
|
|
||||||
|
|
||||||
callback = utils.wrap(callback, unlock);
|
|
||||||
|
|
||||||
hash = tx.hash('hex');
|
hash = tx.hash('hex');
|
||||||
height = tx.height;
|
height = tx.height;
|
||||||
|
|
||||||
@ -1197,14 +1277,9 @@ TXDB.prototype._unconfirm = function unconfirm(tx, info, callback, force) {
|
|||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
batch.write(function(err) {
|
self.emit('unconfirmed', tx, info);
|
||||||
if (err)
|
|
||||||
return callback(err);
|
|
||||||
|
|
||||||
self.emit('unconfirmed', tx, info);
|
return callback(null, true, info);
|
||||||
|
|
||||||
return callback(null, true, info);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1907,7 +1982,7 @@ TXDB.prototype.zap = function zap(id, age, callback, force) {
|
|||||||
utils.forEachSerial(txs, function(tx, next) {
|
utils.forEachSerial(txs, function(tx, next) {
|
||||||
if (tx.ts !== 0)
|
if (tx.ts !== 0)
|
||||||
return next();
|
return next();
|
||||||
self.lazyRemove(tx, next, true);
|
self.remove(tx.hash('hex'), next, true);
|
||||||
}, callback);
|
}, callback);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -313,24 +313,30 @@ describe('Wallet', function() {
|
|||||||
return t + tx.getOutputValue();
|
return t + tx.getOutputValue();
|
||||||
}, 0);
|
}, 0);
|
||||||
assert.equal(total, 154000);
|
assert.equal(total, 154000);
|
||||||
dw.sign(t1, function(err) {
|
dw.getCoins(function(err, coins) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
dw.getBalance(function(err, balance) {
|
dw.sign(t1, function(err) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
assert.equal(balance.total, 11000);
|
dw.getBalance(function(err, balance) {
|
||||||
walletdb.addTX(t1, function(err) {
|
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
dw.getBalance(function(err, balance) {
|
assert.equal(balance.total, 11000);
|
||||||
|
walletdb.addTX(t1, function(err) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
assert.equal(balance.total, 6000);
|
dw.getCoins(function(err, coins) {
|
||||||
dw.getHistory(function(err, txs) {
|
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
assert.equal(txs.length, 2);
|
dw.getBalance(function(err, balance) {
|
||||||
var total = txs.reduce(function(t, tx) {
|
assert.ifError(err);
|
||||||
return t + tx.getOutputValue();
|
assert.equal(balance.total, 6000);
|
||||||
}, 0);
|
dw.getHistory(function(err, txs) {
|
||||||
assert.equal(total, 56000);
|
assert.ifError(err);
|
||||||
cb();
|
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