1289 lines
29 KiB
JavaScript
1289 lines
29 KiB
JavaScript
/**
|
|
* txdb.js - persistent transaction pool
|
|
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
|
|
* https://github.com/indutny/bcoin
|
|
*/
|
|
|
|
var bn = require('bn.js');
|
|
var bcoin = require('../bcoin');
|
|
var utils = bcoin.utils;
|
|
var assert = bcoin.utils.assert;
|
|
var EventEmitter = require('events').EventEmitter;
|
|
|
|
/**
|
|
* TXPool
|
|
*/
|
|
|
|
function TXPool(prefix, db) {
|
|
var self = this;
|
|
|
|
if (!(this instanceof TXPool))
|
|
return new TXPool(wallet, txs);
|
|
|
|
EventEmitter.call(this);
|
|
|
|
this.db = db;
|
|
this.prefix = prefix || 'pool';
|
|
}
|
|
|
|
utils.inherits(TXPool, EventEmitter);
|
|
|
|
TXPool.prototype.add = function add(tx, callback) {
|
|
var self = this;
|
|
|
|
if (Array.isArray(tx)) {
|
|
return utils.forEachSerial(tx, function(tx, next) {
|
|
self.add(tx, next);
|
|
}, callback);
|
|
}
|
|
|
|
this._filter(tx, function(err, result) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
if (!result)
|
|
return callback(null, false);
|
|
|
|
return self._add(tx, callback);
|
|
});
|
|
};
|
|
|
|
TXPool.prototype._filter = function _filter(tx, callback) {
|
|
return callback(null, true);
|
|
};
|
|
|
|
TXPool.prototype._hasAddress = function _hasAddress(address, callback) {
|
|
var p = this.prefix + '/';
|
|
var self = this;
|
|
|
|
callback = utils.ensure(callback);
|
|
|
|
if (!address)
|
|
return callback(null, false);
|
|
|
|
var iter = this.db.db.iterator({
|
|
gte: p + 'a/' + address,
|
|
lte: p + 'a/' + address + '~',
|
|
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(function(err) {
|
|
if (err)
|
|
return callback(err);
|
|
return callback(null, ids);
|
|
});
|
|
}
|
|
|
|
return iter.end(function(err) {
|
|
if (err)
|
|
return callback(err);
|
|
callback(null, true);
|
|
});
|
|
});
|
|
})();
|
|
};
|
|
|
|
TXPool.prototype._addOrphan = function _addOrphan(orphans, orphan) {
|
|
var tx = orphan.tx.toExtended(true);
|
|
var buf;
|
|
|
|
buf = new Buffer(tx.length + 4);
|
|
utils.copy(tx, buf, 0);
|
|
utils.writeU32(buf, orphan.index, tx.length);
|
|
|
|
if (!orphans)
|
|
return buf;
|
|
|
|
return Buffer.concat([orphans, buf]);
|
|
};
|
|
|
|
TXPool.prototype._fromOrphans = function _fromOrphans(buf) {
|
|
var txs = [];
|
|
var tx, index, buf;
|
|
|
|
if (!buf)
|
|
return txs;
|
|
|
|
while (buf.length) {
|
|
tx = bcoin.tx.fromExtended(buf, true);
|
|
index = utils.readU32(buf, tx._extendedSize);
|
|
buf = buf.slice(tx._extendedSize + 4);
|
|
txs.push({ tx: tx, index: index });
|
|
}
|
|
|
|
return txs;
|
|
};
|
|
|
|
TXPool.prototype._add = function add(tx, callback) {
|
|
var self = this;
|
|
var p = this.prefix + '/';
|
|
var hash = tx.hash('hex');
|
|
var updated = false;
|
|
|
|
callback = utils.ensure(callback);
|
|
|
|
var batch = this.db.batch();
|
|
|
|
self.getTX(hash, function(err, existing) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
if (existing) {
|
|
// Tricky - update the tx and coin in storage,
|
|
// and remove pending flag to mark as confirmed.
|
|
if (existing.ts === 0 && tx.ts !== 0) {
|
|
batch.put(p + 't/t/' + hash, tx.toExtended());
|
|
batch.del(p + 'p/' + hash);
|
|
|
|
tx.inputs.forEach(function(input) {
|
|
var type = input.getType();
|
|
var address = input.getAddress();
|
|
var uaddr;
|
|
|
|
if (input.isCoinbase())
|
|
return;
|
|
|
|
if (type === 'pubkey' || type === 'multisig')
|
|
address = null;
|
|
|
|
uaddr = address;
|
|
|
|
if (uaddr) {
|
|
if (!uniq[uaddr])
|
|
uniq[uaddr] = true;
|
|
else
|
|
uaddr = null;
|
|
}
|
|
|
|
if (uaddr)
|
|
batch.del(p + 'p/a/' + uaddr + '/' + hash);
|
|
});
|
|
|
|
tx.outputs.forEach(function(output) {
|
|
var type = output.getType();
|
|
var address = output.getAddress();
|
|
var uaddr, coinRaw;
|
|
|
|
if (type === 'pubkey' || type === 'multisig')
|
|
address = null;
|
|
|
|
uaddr = address;
|
|
|
|
if (uaddr) {
|
|
if (!uniq[uaddr])
|
|
uniq[uaddr] = true;
|
|
else
|
|
uaddr = null;
|
|
}
|
|
|
|
if (uaddr)
|
|
batch.del(p + 'p/a/' + uaddr + '/' + hash);
|
|
|
|
coinRaw = bcoin.protocol.framer.coin({
|
|
version: tx.version,
|
|
height: tx.height,
|
|
value: output.value,
|
|
script: output.script,
|
|
hash: hash,
|
|
index: i,
|
|
spent: false
|
|
}, true);
|
|
|
|
batch.put(p + 'u/t/' + hash + '/' + i, coinRaw);
|
|
});
|
|
|
|
batch.write(function(err) {
|
|
if (err)
|
|
return callback(err);
|
|
self.emit('confirmed', tx);
|
|
self.emit('tx', tx);
|
|
return callback(null, true);
|
|
});
|
|
}
|
|
return callback(null, false);
|
|
}
|
|
|
|
// Consume unspent money or add orphans
|
|
utils.forEachSerial(tx.inputs, function(input, next, i) {
|
|
var key = input.prevout.hash + '/' + input.prevout.index;
|
|
self.getCoin(input.prevout.hash, input.prevout.index, function(err, coin) {
|
|
if (err)
|
|
return next(err);
|
|
|
|
if (coin) {
|
|
// Add TX to inputs and spend money
|
|
input.output = coin;
|
|
|
|
assert(input.prevout.hash === coin.hash);
|
|
assert(input.prevout.index === coin.index);
|
|
|
|
// Skip invalid transactions
|
|
if (!tx.verify(i))
|
|
return callback(null, false);
|
|
|
|
updated = true;
|
|
|
|
var type = input.getType();
|
|
var address = input.getAddress();
|
|
|
|
if (type === 'pubkey' || type === 'multisig')
|
|
address = null;
|
|
|
|
if (input.isCoinbase())
|
|
return next();
|
|
|
|
if (address) {
|
|
batch.del(
|
|
p + 'u/a/' + address
|
|
+ '/' + input.prevout.hash
|
|
+ '/' + input.prevout.index);
|
|
}
|
|
|
|
batch.del(p + 'u/t/' + input.prevout.hash + '/' + input.prevout.index);
|
|
return next();
|
|
}
|
|
|
|
// Only add orphans if this input is ours.
|
|
self._hasAddress(input.getAddress(), function(err, result) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
if (!result)
|
|
return next();
|
|
|
|
// Add orphan, if no parent transaction is yet known
|
|
self.db.get(p + 'o/' + key, function(err, orphans) {
|
|
if (err && err.type !== 'NotFoundError')
|
|
return callback(err);
|
|
|
|
// orphans = self._addOrphan(orphans, { tx: tx, index: i });
|
|
|
|
if (orphans) {
|
|
try {
|
|
orphans = JSON.parse(orphans.toString('utf8'));
|
|
} catch (e) {
|
|
return callback(e);
|
|
}
|
|
} else {
|
|
orphans = [];
|
|
}
|
|
|
|
orphans.push({
|
|
tx: tx.toExtended(true).toString('hex'),
|
|
index: i
|
|
});
|
|
|
|
orphans = new Buffer(JSON.stringify(orphans), 'utf8');
|
|
|
|
batch.put(p + '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) {
|
|
// Do not add unspents for outputs that aren't ours.
|
|
self._hasAddress(output.getAddress(), function(err, result) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
if (!result)
|
|
return next();
|
|
|
|
var coin = bcoin.coin(tx, i);
|
|
|
|
var key = hash + '/' + i;
|
|
|
|
self.db.get(p + 'o/' + key, function(err, orphans) {
|
|
var some;
|
|
|
|
if (err && err.type !== 'NotFoundError')
|
|
return callback(err);
|
|
|
|
if (orphans) {
|
|
try {
|
|
orphans = JSON.parse(orphans.toString('utf8')).map(function(orphan) {
|
|
orphan.tx = bcoin.tx.fromExtended(new Buffer(orphan.tx, 'hex'), true);
|
|
return orphan;
|
|
});
|
|
} catch (e) {
|
|
return next(e);
|
|
}
|
|
}
|
|
|
|
// Add input to orphan
|
|
if (orphans) {
|
|
some = false;
|
|
|
|
utils.forEachSerial(orphans, function(orphan, next, j) {
|
|
if (some)
|
|
return next();
|
|
|
|
orphan.tx.inputs[orphan.index].output = coin;
|
|
|
|
assert(orphan.tx.inputs[orphan.index].prevout.hash === hash);
|
|
assert(orphan.tx.inputs[orphan.index].prevout.index === i);
|
|
|
|
// Verify that input script is correct, if not - add
|
|
// output to unspent and remove orphan from storage
|
|
if (orphan.tx.verify(orphan.index)) {
|
|
some = true;
|
|
return next();
|
|
}
|
|
|
|
self.remove(orphan.tx, function(err) {
|
|
if (err)
|
|
return next(err);
|
|
return next();
|
|
});
|
|
}, function(err) {
|
|
if (err)
|
|
return next(err);
|
|
|
|
if (!some)
|
|
orphans = null;
|
|
|
|
self.db.del(p + 'o/' + key, finish);
|
|
});
|
|
} else {
|
|
finish();
|
|
}
|
|
|
|
function finish(err) {
|
|
if (err)
|
|
return next(err);
|
|
|
|
if (!orphans) {
|
|
var type = output.getType();
|
|
var address = output.getAddress();
|
|
if (type === 'pubkey' || type === 'multisig')
|
|
address = null;
|
|
if (address)
|
|
batch.put(p + 'u/a/' + address + '/' + hash + '/' + i, new Buffer([]));
|
|
batch.put(p + 'u/t/' + hash + '/' + i, coin.toRaw());
|
|
updated = true;
|
|
}
|
|
|
|
next();
|
|
}
|
|
});
|
|
|
|
return true;
|
|
});
|
|
}, function(err) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
batch.put(p + 't/t/' + hash, tx.toExtended());
|
|
if (tx.ts === 0)
|
|
batch.put(p + 'p/' + hash, new Buffer([]));
|
|
|
|
tx.getAddresses().forEach(function(address) {
|
|
batch.put(p + 't/a/' + address + '/' + hash, new Buffer([]));
|
|
if (tx.ts === 0)
|
|
batch.put(p + 'p/a/' + address + '/' + hash, new Buffer([]));
|
|
});
|
|
|
|
batch.write(function(err) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
self.emit('tx', tx);
|
|
|
|
if (tx.ts !== 0)
|
|
self.emit('confirmed', tx);
|
|
|
|
return callback(null, true);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
TXPool.prototype._add_ = function _add(tx, callback) {
|
|
var self = this;
|
|
var p = this.prefix + '/';
|
|
var hash = tx.hash('hex');
|
|
var uniq = {};
|
|
|
|
callback = utils.ensure(callback);
|
|
|
|
this.getTX(hash, function(err, existing) {
|
|
var batch;
|
|
|
|
if (err)
|
|
return callback(err);
|
|
|
|
batch = self.db.batch();
|
|
|
|
if (existing) {
|
|
// Tricky - update the tx and coin in storage,
|
|
// and remove pending flag to mark as confirmed.
|
|
if (existing.ts === 0 && tx.ts !== 0) {
|
|
batch.put(p + 't/t/' + hash, tx.toExtended());
|
|
batch.del(p + 'p/' + hash);
|
|
|
|
tx.inputs.forEach(function(input) {
|
|
var type = input.getType();
|
|
var address = input.getAddress();
|
|
var uaddr;
|
|
|
|
if (input.isCoinbase())
|
|
return;
|
|
|
|
if (type === 'pubkey' || type === 'multisig')
|
|
address = null;
|
|
|
|
uaddr = address;
|
|
|
|
if (uaddr) {
|
|
if (!uniq[uaddr])
|
|
uniq[uaddr] = true;
|
|
else
|
|
uaddr = null;
|
|
}
|
|
|
|
if (uaddr)
|
|
batch.del(p + 'p/a/' + uaddr + '/' + hash);
|
|
});
|
|
|
|
tx.outputs.forEach(function(output) {
|
|
var type = output.getType();
|
|
var address = output.getAddress();
|
|
var uaddr, coinRaw;
|
|
|
|
if (type === 'pubkey' || type === 'multisig')
|
|
address = null;
|
|
|
|
uaddr = address;
|
|
|
|
if (uaddr) {
|
|
if (!uniq[uaddr])
|
|
uniq[uaddr] = true;
|
|
else
|
|
uaddr = null;
|
|
}
|
|
|
|
if (uaddr)
|
|
batch.del(p + 'p/a/' + uaddr + '/' + hash);
|
|
|
|
coinRaw = bcoin.protocol.framer.coin({
|
|
version: tx.version,
|
|
height: tx.height,
|
|
value: output.value,
|
|
script: output.script,
|
|
hash: hash,
|
|
index: i,
|
|
spent: false
|
|
}, true);
|
|
|
|
batch.put(p + 'u/t/' + hash + '/' + i, coinRaw);
|
|
});
|
|
|
|
batch.write(function(err) {
|
|
if (err)
|
|
return callback(err);
|
|
self.emit('confirmed', tx);
|
|
self.emit('tx', tx);
|
|
return callback(null, true);
|
|
});
|
|
}
|
|
return callback(null, false);
|
|
}
|
|
|
|
batch.put(p + 't/t/' + hash, tx.toExtended());
|
|
if (tx.ts === 0)
|
|
batch.put(p + 'p/' + hash, new Buffer([]));
|
|
|
|
tx.inputs.forEach(function(input) {
|
|
var type = input.getType();
|
|
var address = input.getAddress();
|
|
var uaddr;
|
|
|
|
if (input.isCoinbase())
|
|
return;
|
|
|
|
if (type === 'pubkey' || type === 'multisig')
|
|
address = null;
|
|
|
|
uaddr = address;
|
|
|
|
if (uaddr) {
|
|
if (!uniq[uaddr])
|
|
uniq[uaddr] = true;
|
|
else
|
|
uaddr = null;
|
|
}
|
|
|
|
if (uaddr) {
|
|
batch.put(p + 't/a/' + uaddr + '/' + hash, new Buffer([]));
|
|
if (tx.ts === 0)
|
|
batch.put(p + 'p/a/' + uaddr + '/' + hash, new Buffer([]));
|
|
}
|
|
|
|
if (address) {
|
|
batch.del(
|
|
p + 'u/a/' + address
|
|
+ '/' + input.prevout.hash
|
|
+ '/' + input.prevout.index);
|
|
}
|
|
|
|
batch.del(p + 'u/t/' + input.prevout.hash + '/' + input.prevout.index);
|
|
});
|
|
|
|
tx.outputs.forEach(function(output, i) {
|
|
var type = output.getType();
|
|
var address = output.getAddress();
|
|
var uaddr, coinRaw;
|
|
|
|
if (type === 'pubkey' || type === 'multisig')
|
|
address = null;
|
|
|
|
uaddr = address;
|
|
|
|
if (uaddr) {
|
|
if (!uniq[uaddr])
|
|
uniq[uaddr] = true;
|
|
else
|
|
uaddr = null;
|
|
}
|
|
|
|
coinRaw = bcoin.protocol.framer.coin({
|
|
version: tx.version,
|
|
height: tx.height,
|
|
value: output.value,
|
|
script: output.script,
|
|
hash: hash,
|
|
index: i,
|
|
spent: false
|
|
}, true);
|
|
|
|
if (uaddr) {
|
|
batch.put(p + 't/a/' + uaddr + '/' + hash, new Buffer([]));
|
|
if (tx.ts === 0)
|
|
batch.put(p + 'p/a/' + uaddr + '/' + hash, new Buffer([]));
|
|
}
|
|
|
|
if (address)
|
|
batch.put(p + 'u/a/' + address + '/' + hash + '/' + i, new Buffer([]));
|
|
|
|
batch.put(p + 'u/t/' + hash + '/' + i, coinRaw);
|
|
});
|
|
|
|
batch.write(function(err) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
self.emit('tx', tx);
|
|
|
|
if (tx.ts !== 0)
|
|
self.emit('confirmed', tx);
|
|
|
|
return callback(null, true);
|
|
});
|
|
});
|
|
};
|
|
|
|
TXPool.prototype.remove = function remove(hash, callback) {
|
|
var self = this;
|
|
var p = this.prefix + '/';
|
|
var uniq = {};
|
|
|
|
if (hash.hash)
|
|
hash = hash.hash('hex');
|
|
|
|
this.getTX(hash, function(err, tx) {
|
|
var batch;
|
|
|
|
if (err)
|
|
return callback(err);
|
|
|
|
if (!tx)
|
|
return callback(null, true);
|
|
|
|
batch = self.db.batch();
|
|
|
|
assert(tx.hash('hex') === hash);
|
|
|
|
batch.del(p + 't/t/' + hash);
|
|
if (tx.ts === 0)
|
|
batch.del(p + 'p/' + hash);
|
|
|
|
self.fillTX(tx, function(err) {
|
|
if (err)
|
|
return next(err);
|
|
|
|
tx.inputs.forEach(function(input) {
|
|
var type = input.getType();
|
|
var address = input.getAddress();
|
|
var uaddr, coinRaw;
|
|
|
|
if (input.isCoinbase())
|
|
return;
|
|
|
|
if (!input.output)
|
|
return;
|
|
|
|
if (type === 'pubkey' || type === 'multisig')
|
|
address = null;
|
|
|
|
uaddr = address;
|
|
|
|
if (uaddr) {
|
|
if (!uniq[uaddr])
|
|
uniq[uaddr] = true;
|
|
else
|
|
uaddr = null;
|
|
}
|
|
|
|
coinRaw = bcoin.protocol.framer.coin(input.output, true);
|
|
|
|
if (uaddr) {
|
|
batch.del(p + 't/a/' + uaddr + '/' + hash);
|
|
if (tx.ts === 0)
|
|
batch.del(p + 'p/a/' + uaddr + '/' + hash);
|
|
}
|
|
|
|
if (address) {
|
|
batch.put(p + 'u/a/' + address
|
|
+ '/' + input.prevout.hash
|
|
+ '/' + input.prevout.index,
|
|
new Buffer([]));
|
|
}
|
|
|
|
batch.put(p + 'u/t/'
|
|
+ input.prevout.hash
|
|
+ '/' + input.prevout.index,
|
|
coinRaw);
|
|
});
|
|
|
|
tx.outputs.forEach(function(output, i) {
|
|
var type = output.getType();
|
|
var address = output.getAddress();
|
|
var uaddr;
|
|
|
|
if (type === 'pubkey' || type === 'multisig')
|
|
address = null;
|
|
|
|
uaddr = address;
|
|
|
|
if (uaddr) {
|
|
if (!uniq[uaddr])
|
|
uniq[uaddr] = true;
|
|
else
|
|
uaddr = null;
|
|
}
|
|
|
|
if (uaddr) {
|
|
batch.del(p + 't/a/' + uaddr + '/' + hash);
|
|
if (tx.ts === 0)
|
|
batch.del(p + 'p/a/' + uaddr + '/' + hash);
|
|
}
|
|
|
|
if (address)
|
|
batch.del(p + 'u/a/' + address + '/' + hash + '/' + i);
|
|
|
|
batch.del(p + 'u/t/' + hash + '/' + i);
|
|
});
|
|
|
|
batch.write(function(err) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
self.emit('remove tx', tx);
|
|
return callback(null, true);
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
TXPool.prototype.getTXHashes = function getTXHashes(address, callback) {
|
|
var p = this.prefix + '/';
|
|
var self = this;
|
|
var txs = [];
|
|
|
|
callback = utils.ensure(callback);
|
|
|
|
if (Array.isArray(address)) {
|
|
return utils.forEachSerial(address, function(address, next) {
|
|
self.getTXHashes(address, function(err, tx) {
|
|
if (err)
|
|
return next(err);
|
|
|
|
txs = txs.concat(tx);
|
|
|
|
next();
|
|
});
|
|
}, function(err) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
txs = utils.uniqs(txs);
|
|
|
|
return callback(null, txs);
|
|
});
|
|
}
|
|
|
|
var iter = this.db.db.iterator({
|
|
gte: p + 't/a/' + address,
|
|
lte: p + 't/a/' + address + '~',
|
|
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(function(err) {
|
|
if (err)
|
|
return callback(err);
|
|
return callback(null, txs);
|
|
});
|
|
}
|
|
|
|
txs.push(key.split('/')[4]);
|
|
|
|
next();
|
|
});
|
|
})();
|
|
};
|
|
|
|
TXPool.prototype.getPendingHashes = function getPendingHashes(address, callback) {
|
|
var p = this.prefix + '/';
|
|
var self = this;
|
|
var txs = [];
|
|
|
|
callback = utils.ensure(callback);
|
|
|
|
if (Array.isArray(address)) {
|
|
return utils.forEachSerial(address, function(address, next) {
|
|
assert(address);
|
|
self.getPendingHashes(address, function(err, tx) {
|
|
if (err)
|
|
return next(err);
|
|
|
|
txs = txs.concat(tx);
|
|
|
|
next();
|
|
});
|
|
}, function(err) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
txs = utils.uniqs(txs);
|
|
|
|
return callback(null, txs);
|
|
});
|
|
}
|
|
|
|
var iter = this.db.db.iterator({
|
|
gte: address ? p + 'p/a/' + address : p + 'p',
|
|
lte: address ? p + 'p/a/' + address + '~' : p + 'p~',
|
|
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(function(err) {
|
|
if (err)
|
|
return callback(err);
|
|
return callback(null, txs);
|
|
});
|
|
}
|
|
|
|
if (address)
|
|
txs.push(key.split('/')[4]);
|
|
else
|
|
txs.push(key.split('/')[2]);
|
|
|
|
next();
|
|
});
|
|
})();
|
|
};
|
|
|
|
TXPool.prototype.getCoinIDs = function getCoinIDs(address, callback) {
|
|
var p = this.prefix + '/';
|
|
var self = this;
|
|
var coins = [];
|
|
|
|
callback = utils.ensure(callback);
|
|
|
|
if (Array.isArray(address)) {
|
|
return utils.forEachSerial(address, function(address, next) {
|
|
self.getCoinIDs(address, function(err, coin) {
|
|
if (err)
|
|
return next(err);
|
|
|
|
coins = coins.concat(coin);
|
|
|
|
next();
|
|
});
|
|
}, function(err) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
coins = utils.uniqs(coins);
|
|
|
|
return callback(null, coins);
|
|
});
|
|
}
|
|
|
|
var iter = this.db.db.iterator({
|
|
gte: p + 'u/a/' + address,
|
|
lte: p + 'u/a/' + address + '~',
|
|
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(function(err) {
|
|
if (err)
|
|
return callback(err);
|
|
return callback(null, coins);
|
|
});
|
|
}
|
|
|
|
coins.push(key.split('/').slice(4).join('/'));
|
|
|
|
next();
|
|
});
|
|
})();
|
|
};
|
|
|
|
TXPool.prototype.getTXByAddress = function getTXByAddress(address, callback) {
|
|
var self = this;
|
|
var txs = [];
|
|
|
|
return this.getTXHashes(address, function(err, hashes) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
if (!hashes.length)
|
|
return callback(null, hashes);
|
|
|
|
utils.forEachSerial(hashes, function(hash, next) {
|
|
self.getTX(hash, function(err, tx) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
if (!tx)
|
|
return next();
|
|
|
|
txs.push(tx);
|
|
|
|
next();
|
|
});
|
|
}, function(err) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
return callback(null, txs);
|
|
});
|
|
});
|
|
};
|
|
|
|
TXPool.prototype.getLast = function getLast(address, callback) {
|
|
return this.getTXByAddress(address, function(err, txs) {
|
|
var lastTs, lastHeight;
|
|
|
|
if (err)
|
|
return callback(err);
|
|
|
|
lastTs = -1;
|
|
lastHeight = -1;
|
|
|
|
txs.forEach(function(tx) {
|
|
if (tx.ts > lastTs)
|
|
lastTs = tx.ts;
|
|
|
|
if (tx.height > lastHeight)
|
|
lastHeight = tx.height;
|
|
});
|
|
|
|
if (lastTs === -1)
|
|
lastTs = utils.now() - 2 * 7 * 24 * 60 * 60;
|
|
|
|
return callback(null, lastTs, lastHeight);
|
|
});
|
|
};
|
|
|
|
TXPool.prototype.getPendingByAddress = function getPendingByAddress(address, callback) {
|
|
var txs = [];
|
|
|
|
return this.getPendingHashes(address, function(err, hashes) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
if (!hashes.length)
|
|
return callback(null, hashes);
|
|
|
|
utils.forEachSerial(hashes, function(hash, next) {
|
|
self.getTX(hash, function(err, tx) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
if (!tx)
|
|
return next();
|
|
|
|
txs.push(tx);
|
|
|
|
next();
|
|
});
|
|
}, function(err) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
return callback(null, txs);
|
|
});
|
|
});
|
|
};
|
|
|
|
TXPool.prototype.getCoinByAddress = function getCoinByAddress(address, callback) {
|
|
var self = this;
|
|
var coins = [];
|
|
|
|
return this.getCoinIDs(address, function(err, ids) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
if (!ids.length)
|
|
return callback(null, ids);
|
|
|
|
utils.forEachSerial(ids, function(id, next) {
|
|
var parts = id.split('/');
|
|
self.getCoin(parts[0], +parts[1], function(err, coin) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
if (!coin)
|
|
return next();
|
|
|
|
coins.push(coin);
|
|
|
|
next();
|
|
});
|
|
}, function(err) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
return callback(null, coins);
|
|
});
|
|
});
|
|
};
|
|
|
|
TXPool.prototype.fillTX = function fillTX(tx, callback) {
|
|
var self = this;
|
|
|
|
if (Array.isArray(tx)) {
|
|
return utils.forEachSerial(tx, function(tx, next) {
|
|
self.fillTX(tx, function(err) {
|
|
if (err)
|
|
return next(err);
|
|
|
|
next();
|
|
});
|
|
}, callback);
|
|
}
|
|
|
|
callback = utils.asyncify(callback);
|
|
|
|
if (tx.isCoinbase())
|
|
return callback(null, tx);
|
|
|
|
utils.forEach(tx.inputs, function(input, next) {
|
|
if (input.output)
|
|
return next();
|
|
|
|
self.getTX(input.prevout.hash, function(err, tx) {
|
|
if (err)
|
|
return next(err);
|
|
|
|
if (tx)
|
|
input.output = bcoin.coin(tx, input.prevout.index);
|
|
|
|
next();
|
|
});
|
|
}, function(err) {
|
|
if (err)
|
|
return callback(err);
|
|
return callback(null, tx);
|
|
});
|
|
};
|
|
|
|
TXPool.prototype.fillCoin = function fillCoin(tx, callback) {
|
|
var self = this;
|
|
|
|
if (Array.isArray(tx)) {
|
|
return utils.forEachSerial(tx, function(tx, next) {
|
|
self.fillCoin(tx, function(err) {
|
|
if (err)
|
|
return next(err);
|
|
|
|
next();
|
|
});
|
|
}, callback);
|
|
}
|
|
|
|
callback = utils.asyncify(callback);
|
|
|
|
if (tx.isCoinbase())
|
|
return callback(null, tx);
|
|
|
|
utils.forEach(tx.inputs, function(input, next) {
|
|
if (input.output)
|
|
return next();
|
|
|
|
self.getCoin(input.prevout.hash, input.prevout.index, function(err, coin) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
if (coin)
|
|
input.output = coin;
|
|
|
|
next();
|
|
});
|
|
}, function(err) {
|
|
if (err)
|
|
return callback(err);
|
|
return callback(null, tx);
|
|
});
|
|
};
|
|
|
|
TXPool.prototype.getTX = function getTX(hash, callback) {
|
|
var p = this.prefix + '/';
|
|
var id = p + 't/t/' + hash;
|
|
|
|
this.db.get(id, function(err, tx) {
|
|
if (err) {
|
|
if (err.type === 'NotFoundError')
|
|
return callback();
|
|
return callback(err);
|
|
}
|
|
return callback(null, bcoin.tx.fromExtended(tx));
|
|
});
|
|
};
|
|
|
|
TXPool.prototype.getCoin = function getCoin(hash, index, callback) {
|
|
var p = this.prefix + '/';
|
|
var id = p + 'u/t/' + hash + '/' + index;
|
|
|
|
this.db.get(id, function(err, coin) {
|
|
if (err) {
|
|
if (err.type === 'NotFoundError')
|
|
return callback();
|
|
return callback(err);
|
|
}
|
|
|
|
return callback(null, bcoin.coin.fromRaw(coin));
|
|
});
|
|
};
|
|
|
|
TXPool.prototype.getAllByAddress = function getAllByAddress(address, callback) {
|
|
return this.getTXByAddress(address, callback);
|
|
};
|
|
|
|
TXPool.prototype.getUnspentByAddress = function getUnspentByAddress(address, callback) {
|
|
return this.getCoinByAddress(address, callback);
|
|
};
|
|
|
|
TXPool.prototype.getPendingByAddress = function getPendingByAddress(address) {
|
|
return this.getPendingByAddress(address, callback);
|
|
};
|
|
|
|
TXPool.prototype.getBalanceByAddress = function getBalanceByAddress(address, callback) {
|
|
return this.getCoinByAddress(address, function(err, coins) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
coins = coins.reduce(function(acc, coin) {
|
|
return acc.iadd(coin.value);
|
|
}, new bn(0));
|
|
|
|
return callback(null, coins);
|
|
});
|
|
};
|
|
|
|
function WalletPool(db) {
|
|
TXPool.call(this, 'w', db);
|
|
}
|
|
|
|
utils.inherits(WalletPool, TXPool);
|
|
|
|
WalletPool.prototype._filter = function _filter(tx, callback) {
|
|
return this.testTX(tx, callback);
|
|
};
|
|
|
|
WalletPool.prototype.getAll = function getAll(id, callback) {
|
|
var self = this;
|
|
return this.getAddresses(id, function(err, addresses) {
|
|
if (err)
|
|
return callback(err);
|
|
return self.getAllByAddress(addresses, callback);
|
|
});
|
|
};
|
|
|
|
WalletPool.prototype.getUnspent = function getUnspent(id, callback) {
|
|
var self = this;
|
|
return this.getAddresses(id, function(err, addresses) {
|
|
if (err)
|
|
return callback(err);
|
|
return self.getUnspentByAddress(addresses, callback);
|
|
});
|
|
};
|
|
|
|
WalletPool.prototype.getPending = function getPending(id, callback) {
|
|
var self = this;
|
|
return this.getAddresses(id, function(err, addresses) {
|
|
if (err)
|
|
return callback(err);
|
|
return self.getPendingByAddress(addresses, callback);
|
|
});
|
|
};
|
|
|
|
WalletPool.prototype.getBalance = function getBalance(id, callback) {
|
|
var self = this;
|
|
return this.getAddresses(id, function(err, addresses) {
|
|
if (err)
|
|
return callback(err);
|
|
return self.getBalanceByAddress(addresses, callback);
|
|
});
|
|
};
|
|
|
|
WalletPool.prototype.getAddresses = function getAddresses(id, callback) {
|
|
if (typeof id === 'string')
|
|
return callback(null, [id]);
|
|
|
|
if (Array.isArray(id))
|
|
return callback(null, id);
|
|
|
|
if (id.addressMap)
|
|
return callback(null, Object.keys(id.addressMap));
|
|
|
|
if (typeof id === 'object')
|
|
return callback(null, Object.keys(id));
|
|
|
|
return this.db.get('w/w/' + id, function(err, buf) {
|
|
var json;
|
|
|
|
if (err)
|
|
return callback(err);
|
|
|
|
try {
|
|
json = JSON.parse(buf.toString('utf8'));
|
|
} catch (e) {
|
|
return callback(e);
|
|
}
|
|
|
|
return callback(null, Object.keys(json.addressMap));
|
|
});
|
|
};
|
|
|
|
WalletPool.prototype.getIDs = function _getIDs(address, callback) {
|
|
var self = this;
|
|
var ids = [];
|
|
|
|
var iter = this.db.db.iterator({
|
|
gte: 'w/a/' + address,
|
|
lte: 'w/a/' + address + '~',
|
|
keys: true,
|
|
values: false,
|
|
fillCache: false,
|
|
keyAsBuffer: false
|
|
});
|
|
|
|
callback = utils.ensure(callback);
|
|
|
|
(function next() {
|
|
iter.next(function(err, key, value) {
|
|
if (err) {
|
|
return iter.end(function() {
|
|
callback(err);
|
|
});
|
|
}
|
|
|
|
if (key === undefined) {
|
|
return iter.end(function(err) {
|
|
if (err)
|
|
return callback(err);
|
|
return callback(null, ids);
|
|
});
|
|
}
|
|
|
|
ids.push(key.split('/')[3]);
|
|
|
|
next();
|
|
});
|
|
})();
|
|
};
|
|
|
|
WalletPool.prototype.testTX = function test(tx, callback) {
|
|
var self = this;
|
|
|
|
utils.forEachSerial(tx.getAddresses(), function(address, next) {
|
|
self.getIDs(address, function(err, ids) {
|
|
if (err)
|
|
return next(err);
|
|
|
|
if (ids.length > 0)
|
|
return callback(null, true);
|
|
|
|
next();
|
|
});
|
|
}, function(err) {
|
|
if (err)
|
|
return callback(err);
|
|
return callback(null, false);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Expose
|
|
*/
|
|
|
|
module.exports = TXPool;
|