wip
This commit is contained in:
parent
a8eb0f8979
commit
aceddb12d9
@ -1,13 +1,10 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var bitcore = require('bitcore-lib');
|
|
||||||
var BufferReader = bitcore.encoding.BufferReader;
|
|
||||||
|
|
||||||
function Encoding(servicePrefix) {
|
function Encoding(servicePrefix) {
|
||||||
this.servicePrefix = servicePrefix;
|
this.servicePrefix = servicePrefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
Encoding.prototype.encodeAddressIndexKey = function(address, height, txid, index, input) {
|
Encoding.prototype.encodeAddressIndexKey = function(address, height, txid, index, input, timestamp) {
|
||||||
var prefix = new Buffer('00', 'hex');
|
var prefix = new Buffer('00', 'hex');
|
||||||
var buffers = [this.servicePrefix, prefix];
|
var buffers = [this.servicePrefix, prefix];
|
||||||
|
|
||||||
@ -34,29 +31,33 @@ Encoding.prototype.encodeAddressIndexKey = function(address, height, txid, index
|
|||||||
inputBuffer.writeUInt8(input || 0);
|
inputBuffer.writeUInt8(input || 0);
|
||||||
buffers.push(inputBuffer);
|
buffers.push(inputBuffer);
|
||||||
|
|
||||||
|
var timestampBuffer = new Buffer(8);
|
||||||
|
timestampBuffer.writeDoubleBE(timestamp || 0)
|
||||||
|
buffers.push(timestampBuffer);
|
||||||
|
|
||||||
return Buffer.concat(buffers);
|
return Buffer.concat(buffers);
|
||||||
};
|
};
|
||||||
|
|
||||||
Encoding.prototype.decodeAddressIndexKey = function(buffer) {
|
Encoding.prototype.decodeAddressIndexKey = function(buffer) {
|
||||||
var reader = new BufferReader(buffer);
|
|
||||||
reader.read(3);
|
|
||||||
|
|
||||||
var addressSize = reader.readUInt8();
|
var addressSize = buffer.readUInt8(3);
|
||||||
var address = reader.read(addressSize).toString('utf8');
|
var address = buffer.slice(4, addressSize + 4).toString('utf8');
|
||||||
var height = reader.readUInt32BE();
|
var height = buffer.readUInt32BE(addressSize + 4);
|
||||||
var txid = reader.read(32).toString('hex');
|
var txid = buffer.slice(addressSize + 8, addressSize + 40).toString('hex');
|
||||||
var index = reader.readUInt32BE();
|
var index = buffer.readUInt32BE(addressSize + 40);
|
||||||
var input = reader.readUInt8();
|
var input = buffer.readUInt8(addressSize + 44);
|
||||||
|
var timestamp = buffer.readDoubleBE(addressSize + 45);
|
||||||
return {
|
return {
|
||||||
address: address,
|
address: address,
|
||||||
height: height,
|
height: height,
|
||||||
txid: txid,
|
txid: txid,
|
||||||
index: index,
|
index: index,
|
||||||
input: input
|
input: input,
|
||||||
|
timestamp: timestamp
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
Encoding.prototype.encodeUtxoIndexKey = function(address, txid, outputIndex) {
|
Encoding.prototype.encodeUtxoIndexKey = function(address, txid, outputIndex, timestamp) {
|
||||||
var prefix = new Buffer('01', 'hex');
|
var prefix = new Buffer('01', 'hex');
|
||||||
var buffers = [this.servicePrefix, prefix];
|
var buffers = [this.servicePrefix, prefix];
|
||||||
|
|
||||||
@ -78,13 +79,10 @@ Encoding.prototype.encodeUtxoIndexKey = function(address, txid, outputIndex) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Encoding.prototype.decodeUtxoIndexKey = function(buffer) {
|
Encoding.prototype.decodeUtxoIndexKey = function(buffer) {
|
||||||
var reader = new BufferReader(buffer);
|
var addressSize = buffer.readUInt8(3);
|
||||||
reader.read(3);
|
var address = buffer.slice(4, addressSize + 4).toString('utf8');
|
||||||
|
var txid = buffer.slice(addressSize + 4, addressSize + 36).toString('hex');
|
||||||
var addressSize = reader.readUInt8();
|
var outputIndex = buffer.readUInt32BE(addressSize + 36);
|
||||||
var address = reader.read(addressSize).toString('utf8');
|
|
||||||
var txid = reader.read(32).toString('hex');
|
|
||||||
var outputIndex = reader.readUInt32BE(4);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
address: address,
|
address: address,
|
||||||
@ -93,21 +91,25 @@ Encoding.prototype.decodeUtxoIndexKey = function(buffer) {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
Encoding.prototype.encodeUtxoIndexValue = function(height, satoshis, scriptBuffer) {
|
Encoding.prototype.encodeUtxoIndexValue = function(height, satoshis, timestamp, scriptBuffer) {
|
||||||
var heightBuffer = new Buffer(4);
|
var heightBuffer = new Buffer(4);
|
||||||
heightBuffer.writeUInt32BE(height);
|
heightBuffer.writeUInt32BE(height);
|
||||||
var satoshisBuffer = new Buffer(8);
|
var satoshisBuffer = new Buffer(8);
|
||||||
satoshisBuffer.writeDoubleBE(satoshis);
|
satoshisBuffer.writeDoubleBE(satoshis);
|
||||||
return Buffer.concat([heightBuffer, satoshisBuffer, scriptBuffer]);
|
var timestampBuffer = new Buffer(8);
|
||||||
|
timestampBuffer.writeDoubleBE(timestamp || 0)
|
||||||
|
return Buffer.concat([heightBuffer, satoshisBuffer, timestampBuffer, scriptBuffer]);
|
||||||
};
|
};
|
||||||
|
|
||||||
Encoding.prototype.decodeUtxoIndexValue = function(buffer) {
|
Encoding.prototype.decodeUtxoIndexValue = function(buffer) {
|
||||||
var height = buffer.readUInt32BE();
|
var height = buffer.readUInt32BE();
|
||||||
var satoshis = buffer.readDoubleBE(4);
|
var satoshis = buffer.readDoubleBE(4);
|
||||||
var scriptBuffer = buffer.slice(12);
|
var timestamp = buffer.readDoubleBE(12);
|
||||||
|
var scriptBuffer = buffer.slice(20);
|
||||||
return {
|
return {
|
||||||
height: height,
|
height: height,
|
||||||
satoshis: satoshis,
|
satoshis: satoshis,
|
||||||
|
timestamp: timestamp,
|
||||||
script: scriptBuffer
|
script: scriptBuffer
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5,7 +5,6 @@ var inherits = require('util').inherits;
|
|||||||
var async = require('async');
|
var async = require('async');
|
||||||
var index = require('../../');
|
var index = require('../../');
|
||||||
var log = index.log;
|
var log = index.log;
|
||||||
var errors = index.errors;
|
|
||||||
var bitcore = require('bitcore-lib');
|
var bitcore = require('bitcore-lib');
|
||||||
var Unit = bitcore.Unit;
|
var Unit = bitcore.Unit;
|
||||||
var _ = bitcore.deps._;
|
var _ = bitcore.deps._;
|
||||||
@ -15,96 +14,212 @@ var Transform = require('stream').Transform;
|
|||||||
|
|
||||||
var AddressService = function(options) {
|
var AddressService = function(options) {
|
||||||
BaseService.call(this, options);
|
BaseService.call(this, options);
|
||||||
this._txService = this.node.services.transaction;
|
this._db = this.node.services.db;
|
||||||
|
this._tx = this.node.services.transaction;
|
||||||
this._network = this.node.getNetworkName();
|
this._network = this.node.getNetworkName();
|
||||||
|
this._p2p = this.node.services.p2p;
|
||||||
};
|
};
|
||||||
|
|
||||||
inherits(AddressService, BaseService);
|
inherits(AddressService, BaseService);
|
||||||
|
|
||||||
AddressService.dependencies = [
|
AddressService.dependencies = [
|
||||||
'bitcoind',
|
'p2p',
|
||||||
'db',
|
'db',
|
||||||
'block',
|
'block',
|
||||||
'transaction'
|
'transaction'
|
||||||
];
|
];
|
||||||
|
|
||||||
// ---- public function prototypes
|
// ---- public function prototypes
|
||||||
AddressService.prototype.getUtxos = function(addresses, queryMempool, callback) {
|
AddressService.prototype.getAddressHistory = function(addresses, options, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if(!Array.isArray(addresses)) {
|
options = options || {};
|
||||||
addresses = [addresses];
|
options.from = options.from || 0;
|
||||||
}
|
options.to = options.to || 0xffffffff;
|
||||||
|
options.queryMempool = _.isUndefined(options.queryMempool) ? true : false;
|
||||||
|
|
||||||
var utxos = [];
|
async.mapLimit(addresses, 4, function(address, next) {
|
||||||
|
|
||||||
async.eachSeries(addresses, function(address, next) {
|
self._getAddressHistory(address, options, next);
|
||||||
self.getUtxosForAddress(address, queryMempool, function(err, unspents) {
|
|
||||||
if(err && err instanceof errors.NoOutputs) {
|
}, function(err, res) {
|
||||||
return next();
|
|
||||||
} else if(err) {
|
if(err) {
|
||||||
return next(err);
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
var results = {
|
||||||
|
totalItems: res.length,
|
||||||
|
from: options.from,
|
||||||
|
to: options.to,
|
||||||
|
items: res
|
||||||
|
};
|
||||||
|
|
||||||
|
callback(null, results);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
AddressService.prototype.getAddressSummary = function(address, options, callback) {
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
options.from = options.from || 0;
|
||||||
|
options.to = options.to || 0xffffffff;
|
||||||
|
options.queryMempool = _.isUndefined(options.queryMempool) ? true : false;
|
||||||
|
|
||||||
|
var result = {
|
||||||
|
addrStr: address,
|
||||||
|
balance: 0,
|
||||||
|
balanceSat: 0,
|
||||||
|
totalReceived: 0,
|
||||||
|
totalReceivedSat: 0,
|
||||||
|
totalSent: 0,
|
||||||
|
totalSentSat: 0,
|
||||||
|
unconfirmedBalance: 0,
|
||||||
|
unconfirmedBalanceSat: 0,
|
||||||
|
unconfirmedTxApperances: 0,
|
||||||
|
txApperances: 0,
|
||||||
|
transactions: []
|
||||||
|
};
|
||||||
|
|
||||||
|
// txid criteria
|
||||||
|
var start = self._encoding.encodeAddressIndexKey(address, options.from);
|
||||||
|
var end = self._encoding.encodeAddressIndexKey(address, options.to);
|
||||||
|
|
||||||
|
var criteria = {
|
||||||
|
gte: start,
|
||||||
|
lt: end
|
||||||
|
};
|
||||||
|
|
||||||
|
// txid stream
|
||||||
|
var txidStream = self._db.createKeyStream(criteria);
|
||||||
|
|
||||||
|
txidStream.on('close', function() {
|
||||||
|
txidStream.unpipe();
|
||||||
|
});
|
||||||
|
|
||||||
|
// tx stream
|
||||||
|
var txStream = new Transform({ objectMode: true, highWaterMark: 1000 });
|
||||||
|
txStream.on('end', function() {
|
||||||
|
result.balance = Unit.fromSatoshis(result.balanceSat).toBTC();
|
||||||
|
result.totalReceived = Unit.fromSatoshis(result.totalReceivedSat).toBTC();
|
||||||
|
result.totalSent = Unit.fromSatoshis(result.totalSentSat).toBTC();
|
||||||
|
result.unconfirmedBalance = Unit.fromSatoshis(result.unconfirmedBalanceSat).toBTC();
|
||||||
|
callback(null, result);
|
||||||
|
});
|
||||||
|
|
||||||
|
// pipe txids into tx stream for processing
|
||||||
|
txidStream.pipe(txStream);
|
||||||
|
|
||||||
|
txStream._transform = function(chunk, enc, callback) {
|
||||||
|
|
||||||
|
var key = self._encoding.decodeAddressIndexKey(chunk);
|
||||||
|
|
||||||
|
self._tx.getTransaction(key.txid, options, function(err, tx) {
|
||||||
|
|
||||||
|
if(err) {
|
||||||
|
log.error(err);
|
||||||
|
txStream.emit('error', err);
|
||||||
|
return callback();
|
||||||
}
|
}
|
||||||
|
|
||||||
utxos = utxos.concat(unspents);
|
if (!tx) {
|
||||||
next();
|
log.error('Could not find tx for txid: ' + key.txid + '. This should not be possible, check indexes.');
|
||||||
|
return callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
var confirmations = self._p2p.getBestHeight() - key.height;
|
||||||
|
result.transactions.push(tx.txid());
|
||||||
|
result.txApperances++;
|
||||||
|
|
||||||
|
if (key.input) {
|
||||||
|
|
||||||
|
result.balanceSat -= tx.__inputValues[key.index];
|
||||||
|
result.totalSentSat += tx.__inputValues[key.index];
|
||||||
|
|
||||||
|
if (confirmations < 1) {
|
||||||
|
result.unconfirmedBalanceSat -= tx.__inputValues[key.index];
|
||||||
|
result.unconfirmedTxApperances++;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
result.balanceSat += tx.outputs[key.index].value;
|
||||||
|
result.totalReceivedSat += tx.outputs[key.index].value;
|
||||||
|
|
||||||
|
if (confirmations < 1) {
|
||||||
|
result.unconfirmedBalanceSat += tx.inputValues[key.index];
|
||||||
|
result.unconfirmedTxApperances++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callback();
|
||||||
|
|
||||||
});
|
});
|
||||||
}, function(err) {
|
|
||||||
callback(err, utxos);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
AddressService.prototype.getUtxosForAddress = function(address, queryMempool, callback) {
|
};
|
||||||
|
|
||||||
var self = this;
|
txStream.on('error', function(err) {
|
||||||
|
log.error(err);
|
||||||
var stream = self.db.createReadStream({
|
txStream.unpipe();
|
||||||
gte: self._encoding.encodeUtxoIndexKey(address),
|
|
||||||
lt: self._encoding.encodeUtxoIndexKey(utils.getTerminalKey(new Buffer(address)))
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var utxos = [];
|
txStream._flush = function(callback) {
|
||||||
stream.on('data', function(data) {
|
txStream.emit('end');
|
||||||
var key = self._encoding.decodeUtxoIndexKey(data.key);
|
|
||||||
var value = self._encoding.decodeUtxoIndexValue(data.value);
|
|
||||||
utxos.push({
|
|
||||||
address: key.address,
|
|
||||||
txid: key.txid,
|
|
||||||
outputIndex: key.outputIndex,
|
|
||||||
satoshis: value.satoshis,
|
|
||||||
height: value.height,
|
|
||||||
script: value.script
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
stream.on('end', function() {
|
|
||||||
return callback(null, utxos);
|
|
||||||
});
|
|
||||||
stream.on('error', function(err) {
|
|
||||||
if(err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
AddressService.prototype.start = function(callback) {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
self._setListeners();
|
|
||||||
|
|
||||||
this.db = this.node.services.db;
|
|
||||||
this.db.getPrefix(this.name, function(err, prefix) {
|
|
||||||
if(err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
self.prefix = prefix;
|
|
||||||
self._encoding = new Encoding(self.prefix);
|
|
||||||
callback();
|
callback();
|
||||||
});
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
AddressService.prototype.stop = function(callback) {
|
AddressService.prototype.getAddressUnspentOutputs = function(address, options, callback) {
|
||||||
setImmediate(callback);
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
options.from = options.from || 0;
|
||||||
|
options.to = options.to || 0xffffffff;
|
||||||
|
options.queryMempool = _.isUndefined(options.queryMempool) ? true : false;
|
||||||
|
|
||||||
|
var results = [];
|
||||||
|
|
||||||
|
var start = self._encoding.encodeUtxoIndexKey(address);
|
||||||
|
var criteria = {
|
||||||
|
gte: start,
|
||||||
|
lt: utils.getTerminalKey(start)
|
||||||
|
};
|
||||||
|
|
||||||
|
var utxoStream = self._db.createReadStream(criteria);
|
||||||
|
|
||||||
|
var streamErr;
|
||||||
|
utxoStream.on('end', function() {
|
||||||
|
if (streamErr) {
|
||||||
|
return callback(streamErr);
|
||||||
|
}
|
||||||
|
callback(null, results);
|
||||||
|
});
|
||||||
|
|
||||||
|
utxoStream.on('error', function(err) {
|
||||||
|
streamErr = err;
|
||||||
|
});
|
||||||
|
|
||||||
|
utxoStream.on('data', function(data) {
|
||||||
|
var key = self._encoding.decodeUtxoIndexKey(data.key);
|
||||||
|
var value = self._encoding.decodeUtxoIndexValue(data.value);
|
||||||
|
results.push({
|
||||||
|
address: address,
|
||||||
|
txid: key.txid,
|
||||||
|
vout: key.outputIndex,
|
||||||
|
ts: value.timestamp,
|
||||||
|
scriptPubKey: value.script.toString('hex'),
|
||||||
|
amount: Unit.fromSatoshis(value.satoshis).toBTC(),
|
||||||
|
confirmations: self._p2p.getBestHeight() - value.height,
|
||||||
|
satoshis: value.satoshis,
|
||||||
|
confirmationsFromCache: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
AddressService.prototype.getAPIMethods = function() {
|
AddressService.prototype.getAPIMethods = function() {
|
||||||
@ -116,46 +231,45 @@ AddressService.prototype.getAPIMethods = function() {
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
AddressService.prototype.getAddressHistory = function(addresses, options, callback) {
|
AddressService.prototype.start = function(callback) {
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this._db.getPrefix(this.name, function(err, prefix) {
|
||||||
|
if(err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
self._encoding = new Encoding(prefix);
|
||||||
|
self._startSubscriptions();
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
AddressService.prototype.stop = function(callback) {
|
||||||
|
setImmediate(callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// ---- start private function prototypes
|
||||||
|
AddressService.prototype._getAddressHistory = function(address, options, callback) {
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
options = options || {};
|
options = options || {};
|
||||||
var from = options.from || 0;
|
var from = options.from || 0;
|
||||||
var to = options.to || 0xffffffff;
|
var to = options.to || 0xffffffff;
|
||||||
|
|
||||||
async.mapLimit(addresses, 4, function(address, next) {
|
if (_.isUndefined(options.queryMempool)) {
|
||||||
|
options.queryMempool = true;
|
||||||
self._getAddressHistory(address, next);
|
}
|
||||||
|
|
||||||
}, function(err, res) {
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
var results = {
|
|
||||||
totalItems: res.length,
|
|
||||||
from: from,
|
|
||||||
to: to,
|
|
||||||
items: res
|
|
||||||
};
|
|
||||||
|
|
||||||
callback(null, results);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
AddressService.prototype._getAddressHistory = function(address, options, callback) {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
var results = [];
|
var results = [];
|
||||||
var start = self._encoding.encodeAddressIndexKey(address);
|
var start = self._encoding.encodeAddressIndexKey(address, options.start);
|
||||||
|
var end = self._encoding.encodeAddressIndexKey(address, options.end);
|
||||||
|
|
||||||
var criteria = {
|
var criteria = {
|
||||||
gte: start,
|
gte: start,
|
||||||
lte: utils.getTerminalKey(start)
|
lte: end
|
||||||
};
|
};
|
||||||
|
|
||||||
// txid stream
|
// txid stream
|
||||||
@ -181,9 +295,9 @@ AddressService.prototype._getAddressHistory = function(address, options, callbac
|
|||||||
|
|
||||||
txStream._transform = function(chunk, enc, callback) {
|
txStream._transform = function(chunk, enc, callback) {
|
||||||
|
|
||||||
var key = self._encoding.decodeWalletTransactionKey(chunk);
|
var key = self._encoding.decodeAddressIndexKey(chunk);
|
||||||
|
|
||||||
self._tx.getDetailedTransaction(key.txid, options, function(err, tx) {
|
self._tx.getTransaction(key.txid, options, function(err, tx) {
|
||||||
|
|
||||||
if(err) {
|
if(err) {
|
||||||
log.error(err);
|
log.error(err);
|
||||||
@ -216,177 +330,6 @@ AddressService.prototype._getAddressHistory = function(address, options, callbac
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
AddressService.prototype.getAddressSummary = function(address, options, callback) {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if (_.isUndefined(options.queryMempool)) {
|
|
||||||
options.queryMempool = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = {
|
|
||||||
addrStr: address,
|
|
||||||
balance: 0,
|
|
||||||
balanceSat: 0,
|
|
||||||
totalReceived: 0,
|
|
||||||
totalReceivedSat: 0,
|
|
||||||
totalSent: 0,
|
|
||||||
totalSentSat: 0,
|
|
||||||
unconfirmedBalance: 0,
|
|
||||||
unconfirmedBalanceSat: 0,
|
|
||||||
unconfirmedTxApperances: 0,
|
|
||||||
txApperances: 0,
|
|
||||||
transactions: []
|
|
||||||
};
|
|
||||||
|
|
||||||
// txid criteria
|
|
||||||
var start = self._encoding.encodeAddressIndexKey(address);
|
|
||||||
var criteria = {
|
|
||||||
gte: start,
|
|
||||||
lte: utils.getTerminalKey(start)
|
|
||||||
};
|
|
||||||
|
|
||||||
// txid stream
|
|
||||||
var txidStream = self._db.createKeyStream(criteria);
|
|
||||||
|
|
||||||
txidStream.on('close', function() {
|
|
||||||
txidStream.unpipe();
|
|
||||||
});
|
|
||||||
|
|
||||||
// tx stream
|
|
||||||
var txStream = new Transform({ objectMode: true, highWaterMark: 1000 });
|
|
||||||
txStream.on('end', function() {
|
|
||||||
result.balance = Unit.fromSatoshis(result.balanceSat).toBTC();
|
|
||||||
result.totalReceived = Unit.fromSatoshis(result.totalReceivedSat).toBTC();
|
|
||||||
result.totalSent = Unit.fromSatoshis(result.totalSentSat).toBTC();
|
|
||||||
result.unconfirmedBalance = Unit.fromSatoshis(result.unconfirmedBalanceSat).toBTC();
|
|
||||||
callback(null, result);
|
|
||||||
});
|
|
||||||
|
|
||||||
// pipe txids into tx stream for processing
|
|
||||||
txidStream.pipe(txStream);
|
|
||||||
|
|
||||||
txStream._transform = function(chunk, enc, callback) {
|
|
||||||
|
|
||||||
var key = self._encoding.decodeWalletTransactionKey(chunk);
|
|
||||||
|
|
||||||
self._tx.getTransaction(key.txid, options, function(err, res) {
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
log.error(err);
|
|
||||||
txStream.emit('error', err);
|
|
||||||
return callback();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!res) {
|
|
||||||
log.error('Could not find tx for txid: ' + key.txid + '. This should not be possible, check indexes.');
|
|
||||||
return callback();
|
|
||||||
}
|
|
||||||
|
|
||||||
var tx = res.tx;
|
|
||||||
|
|
||||||
result.transactions.push(tx.id);
|
|
||||||
result.txApperances++;
|
|
||||||
|
|
||||||
if (key.input) {
|
|
||||||
|
|
||||||
result.balanceSat -= tx.inputValues[key.index];
|
|
||||||
result.totalSentSat += tx.inputValues[key.index];
|
|
||||||
|
|
||||||
if (res.confirmations === 0) {
|
|
||||||
|
|
||||||
result.unconfirmedBalanceSat -= tx.inputValues[key.index];
|
|
||||||
result.unconfirmedTxApperances++;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
result.balanceSat += tx.outputs[key.index].satoshis;
|
|
||||||
result.totalReceivedSat += tx.outputs[key.index].satoshis;
|
|
||||||
|
|
||||||
if (res.confirmations === 0) {
|
|
||||||
|
|
||||||
result.unconfirmedBalanceSat += tx.inputValues[key.index];
|
|
||||||
result.unconfirmedTxApperances++;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
callback();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
txStream.on('error', function(err) {
|
|
||||||
log.error(err);
|
|
||||||
txStream.unpipe();
|
|
||||||
});
|
|
||||||
|
|
||||||
txStream._flush = function(callback) {
|
|
||||||
txStream.emit('end');
|
|
||||||
callback();
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
AddressService.prototype.getAddressUnspentOutputs = function(address, options, callback) {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
if (_.isUndefined(options.queryMempool)) {
|
|
||||||
options.queryMempool = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var results = [];
|
|
||||||
|
|
||||||
var start = self._encoding.encodeUtxoIndexKey(address);
|
|
||||||
var criteria = {
|
|
||||||
gte: start,
|
|
||||||
lt: utils.getTerminalKey(start)
|
|
||||||
};
|
|
||||||
|
|
||||||
var utxoStream = self._db.createReadStream(criteria);
|
|
||||||
|
|
||||||
var streamErr;
|
|
||||||
utxoStream.on('end', function() {
|
|
||||||
if (streamErr) {
|
|
||||||
return callback(streamErr);
|
|
||||||
}
|
|
||||||
callback(null, results);
|
|
||||||
});
|
|
||||||
|
|
||||||
utxoStream.on('error', function(err) {
|
|
||||||
streamErr = err;
|
|
||||||
});
|
|
||||||
|
|
||||||
utxoStream.on('data', function(data) {
|
|
||||||
var key = self._decodeUtxoIndexKey(data.key);
|
|
||||||
var value = self._encoding.decodeUtxoIndexValue(data.value);
|
|
||||||
results.push({
|
|
||||||
address: address,
|
|
||||||
txid: key.txid,
|
|
||||||
vout: key.oudputIndex,
|
|
||||||
ts: null,
|
|
||||||
scriptPubKey: value.scriptBuffer.toString('hex'),
|
|
||||||
amount: Unit.fromSatoshis(value.satoshis).toBTC(),
|
|
||||||
confirmations: self._p2p.getBestHeight() - value.height,
|
|
||||||
satoshis: value.satoshis,
|
|
||||||
confirmationsFromCache: true
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// ---- private function prototypes
|
|
||||||
AddressService.prototype._setListeners = function() {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
self.on('reorg', self._handleReorg.bind(self));
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
AddressService.prototype._startSubscriptions = function() {
|
AddressService.prototype._startSubscriptions = function() {
|
||||||
|
|
||||||
if (this._subscribed) {
|
if (this._subscribed) {
|
||||||
@ -408,12 +351,12 @@ AddressService.prototype._startSubscriptions = function() {
|
|||||||
AddressService.prototype._onReorg = function(oldBlockList, newBlockList, commonAncestor) {
|
AddressService.prototype._onReorg = function(oldBlockList, newBlockList, commonAncestor) {
|
||||||
|
|
||||||
// if the common ancestor block height is greater than our own, then nothing to do for the reorg
|
// if the common ancestor block height is greater than our own, then nothing to do for the reorg
|
||||||
if (this._tip.height <= commonAncestor.header.height) {
|
if (this._tip.height <= commonAncestor.height) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the tip to the common ancestor in case something goes wrong with the reorg
|
// set the tip to the common ancestor in case something goes wrong with the reorg
|
||||||
var tipOps = utils.encodeTip({ hash: commonAncestor.hash, height: commonAncestor.header.height }, this.name);
|
var tipOps = utils.encodeTip({ hash: commonAncestor.hash, height: commonAncestor.height }, this.name);
|
||||||
|
|
||||||
var removalOps = [{
|
var removalOps = [{
|
||||||
type: 'put',
|
type: 'put',
|
||||||
|
|||||||
@ -14,6 +14,9 @@ describe('Address service encoding', function() {
|
|||||||
var addressSizeBuf = new Buffer(1);
|
var addressSizeBuf = new Buffer(1);
|
||||||
var prefix0 = new Buffer('00', 'hex');
|
var prefix0 = new Buffer('00', 'hex');
|
||||||
var prefix1 = new Buffer('01', 'hex');
|
var prefix1 = new Buffer('01', 'hex');
|
||||||
|
var ts = Math.floor(new Date('2017-02-28').getTime() / 1000);
|
||||||
|
var tsBuf = new Buffer(8);
|
||||||
|
tsBuf.writeDoubleBE(ts);
|
||||||
addressSizeBuf.writeUInt8(address.length);
|
addressSizeBuf.writeUInt8(address.length);
|
||||||
var addressIndexKeyBuf = Buffer.concat([
|
var addressIndexKeyBuf = Buffer.concat([
|
||||||
servicePrefix,
|
servicePrefix,
|
||||||
@ -23,7 +26,8 @@ describe('Address service encoding', function() {
|
|||||||
new Buffer('00000001', 'hex'),
|
new Buffer('00000001', 'hex'),
|
||||||
new Buffer(txid, 'hex'),
|
new Buffer(txid, 'hex'),
|
||||||
new Buffer('00000000', 'hex'),
|
new Buffer('00000000', 'hex'),
|
||||||
new Buffer('00', 'hex')
|
new Buffer('00', 'hex'),
|
||||||
|
tsBuf
|
||||||
]);
|
]);
|
||||||
var outputIndex = 5;
|
var outputIndex = 5;
|
||||||
var utxoKeyBuf = Buffer.concat([
|
var utxoKeyBuf = Buffer.concat([
|
||||||
@ -38,10 +42,10 @@ describe('Address service encoding', function() {
|
|||||||
var sats = tx.outputs[0].satoshis;
|
var sats = tx.outputs[0].satoshis;
|
||||||
var satsBuf = new Buffer(8);
|
var satsBuf = new Buffer(8);
|
||||||
satsBuf.writeDoubleBE(sats);
|
satsBuf.writeDoubleBE(sats);
|
||||||
var utxoValueBuf = Buffer.concat([new Buffer('00000001', 'hex'), satsBuf, tx.outputs[0]._scriptBuffer]);
|
var utxoValueBuf = Buffer.concat([new Buffer('00000001', 'hex'), satsBuf, tsBuf, tx.outputs[0]._scriptBuffer]);
|
||||||
|
|
||||||
it('should encode address key' , function() {
|
it('should encode address key' , function() {
|
||||||
encoding.encodeAddressIndexKey(address, height, txid).should.deep.equal(addressIndexKeyBuf);
|
encoding.encodeAddressIndexKey(address, height, txid, 0, 0, ts).should.deep.equal(addressIndexKeyBuf);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should decode address key', function() {
|
it('should decode address key', function() {
|
||||||
@ -65,6 +69,7 @@ describe('Address service encoding', function() {
|
|||||||
encoding.encodeUtxoIndexValue(
|
encoding.encodeUtxoIndexValue(
|
||||||
height,
|
height,
|
||||||
tx.outputs[0].satoshis,
|
tx.outputs[0].satoshis,
|
||||||
|
ts,
|
||||||
tx.outputs[0]._scriptBuffer).should.deep.equal(utxoValueBuf);
|
tx.outputs[0]._scriptBuffer).should.deep.equal(utxoValueBuf);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -73,6 +78,7 @@ describe('Address service encoding', function() {
|
|||||||
utxoValue.height.should.equal(height);
|
utxoValue.height.should.equal(height);
|
||||||
utxoValue.satoshis.should.equal(sats);
|
utxoValue.satoshis.should.equal(sats);
|
||||||
utxoValue.script.should.deep.equal(tx.outputs[0]._scriptBuffer);
|
utxoValue.script.should.deep.equal(tx.outputs[0]._scriptBuffer);
|
||||||
|
utxoValue.timestamp.should.equal(ts);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,48 +1,222 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var should = require('chai').should();
|
var sinon = require('sinon');
|
||||||
var bitcore = require('bitcore-lib');
|
|
||||||
var Script = bitcore.Script;
|
|
||||||
var PrivateKey = bitcore.PrivateKey;
|
|
||||||
|
|
||||||
|
|
||||||
var AddressService = require('../../../lib/services/address');
|
var AddressService = require('../../../lib/services/address');
|
||||||
|
var Tx = require('bcoin').tx;
|
||||||
|
var expect = require('chai').expect;
|
||||||
|
var Encoding = require('../../../lib/services/address/encoding');
|
||||||
|
var Readable = require('stream').Readable;
|
||||||
|
var EventEmitter = require('events').EventEmitter;
|
||||||
var utils = require('../../../lib/utils');
|
var utils = require('../../../lib/utils');
|
||||||
|
|
||||||
describe('Address Service', function() {
|
describe('Address Service', function() {
|
||||||
var address;
|
|
||||||
|
|
||||||
var sig = new Buffer('3045022100e8b654c91770402bf35d207406c7d4967605f99478954c8030cf7060160b5c730220296690debdd354d5fa17a61379cfdce9fdea136a4b234664e41c1c7cd840098901', 'hex');
|
var tx = Tx.fromRaw( '0100000004de9b4bb17f627096a9ee0b4528e4eae17df5b5c69edc29704c2e84a7371db29f010000006b483045022100f5b1a0d33b7be291c3953c25f8ae39d98601aa7099a8674daf638a08b86c7173022006ce372da5ad088a1cc6e5c49c2760a1b6f085eb1b51b502211b6bc9508661f9012102ec5e3731e54475dd2902326f43602a03ae3d62753324139163f81f20e787514cffffffff7a1d4e5fc2b8177ec738cd723a16cf2bf493791e55573445fc0df630fe5e2d64010000006b483045022100cf97f6cb8f126703e9768545dfb20ffb10ba78ae3d101aa46775f5a239b075fc02203150c4a89a11eaf5e404f4f96b62efa4455e9525765a025525c7105a7e47b6db012102c01e11b1d331f999bbdb83e8831de503cd52a01e3834a95ccafd615c67703d77ffffffff9e52447116415ca0d0567418a1a4ef8f27be3ff5a96bf87c922f3723d7db5d7c000000006b483045022100f6c117e536701be41a6b0b544d7c3b1091301e4e64a6265b6eb167b15d16959d022076916de4b115e700964194ce36a24cb9105f86482f4abbc63110c3f537cd5770012102ddf84cc7bee2d6a82ac09628a8ad4a26cd449fc528b81e7e6cc615707b8169dfffffffff5815d9750eb3572e30d6fd9df7afb4dbd76e042f3aa4988ac763b3fdf8397f80010000006a473044022028f4402b736066d93d2a32b28ccd3b7a21d84bb58fcd07fe392a611db94cdec5022018902ee0bf2c3c840c1b81ead4e6c87c88c48b2005bf5eea796464e561a620a8012102b6cdd1a6cd129ef796faeedb0b840fcd0ca00c57e16e38e46ee7028d59812ae7ffffffff0220a10700000000001976a914c342bcd1a7784d9842f7386b8b3b8a3d4171a06e88ac59611100000000001976a91449f8c749a9960dc29b5cbe7d2397cea7d26611bb88ac00000000', 'hex');
|
||||||
|
var addressService;
|
||||||
|
var sandbox;
|
||||||
|
|
||||||
var pks = [ new PrivateKey(), new PrivateKey() ];
|
beforeEach(function() {
|
||||||
|
sandbox = sinon.sandbox.create();
|
||||||
var pubKeys = [ pks[0].publicKey, pks[1].publicKey ];
|
addressService = new AddressService({
|
||||||
|
node: {
|
||||||
var scripts = {
|
getNetworkName: function() { return 'regtest'; },
|
||||||
p2pkhIn: Script.buildPublicKeyHashIn(pubKeys[0], sig),
|
services: []
|
||||||
p2pkhOut: Script.buildPublicKeyHashOut(pubKeys[0]),
|
}
|
||||||
p2shIn: Script.buildP2SHMultisigIn(pubKeys, 2, [sig, sig]),
|
});
|
||||||
p2shOut: Script.buildScriptHashOut(Script.fromAddress(pks[0].toAddress())),
|
addressService._encoding = new Encoding(new Buffer('0000', 'hex'));
|
||||||
p2pkIn: Script.buildPublicKeyIn(sig),
|
|
||||||
p2pkOut: Script.buildPublicKeyOut(pubKeys[0]),
|
|
||||||
p2bmsIn: Script.buildMultisigIn(pubKeys, 2, [sig, sig]),
|
|
||||||
p2bmsOut: Script.buildMultisigOut(pubKeys, 2)
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
before(function(done) {
|
|
||||||
address = new AddressService({ node: { name: 'address' } });
|
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should get an address from a script buffer', function() {
|
afterEach(function() {
|
||||||
var start = process.hrtime();
|
sandbox.restore();
|
||||||
for(var key in scripts) {
|
});
|
||||||
var ret = address.getAddressString({ script: scripts[key] });
|
|
||||||
console.log(ret);
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log(utils.diffTime(start));
|
describe('#start', function() {
|
||||||
|
|
||||||
|
it('should get prefix for database', function(done) {
|
||||||
|
var startSubs = sandbox.stub(addressService, '_startSubscriptions');
|
||||||
|
var getPrefix = sandbox.stub().callsArgWith(1, null, new Buffer('ffee', 'hex'));
|
||||||
|
addressService._db = { getPrefix: getPrefix };
|
||||||
|
addressService.start(function() {
|
||||||
|
expect(startSubs.calledOnce).to.be.true;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#stop', function() {
|
||||||
|
it('should stop the service', function(done) {
|
||||||
|
addressService.stop(function() {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('#getAddressHistory', function() {
|
||||||
|
it('should get the address history', function(done) {
|
||||||
|
|
||||||
|
sandbox.stub(addressService, '_getAddressHistory').callsArgWith(2, null, {});
|
||||||
|
addressService.getAddressHistory(['a', 'b', 'c'], { from: 12, to: 14 }, function(err, res) {
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(res).to.be.deep.equal({
|
||||||
|
totalItems: 3,
|
||||||
|
from: 12,
|
||||||
|
to: 14,
|
||||||
|
items: [ {}, {}, {} ]
|
||||||
|
});
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#_getAddresHistory', function() {
|
||||||
|
it('should get the address history', function(done) {
|
||||||
|
var encoding = new Encoding(new Buffer('0001', 'hex'));
|
||||||
|
addressService._encoding = encoding;
|
||||||
|
var address = 'a';
|
||||||
|
var opts = { from: 12, to: 14 };
|
||||||
|
var txid = '1c6ea4a55a3edaac0a05e93b52908f607376a8fdc5387c492042f8baa6c05085';
|
||||||
|
var data = [ null, encoding.encodeAddressIndexKey(address, 123, txid, 1, 1) ];
|
||||||
|
var getTransaction = sandbox.stub().callsArgWith(2, null, {});
|
||||||
|
addressService._tx = { getTransaction: getTransaction };
|
||||||
|
|
||||||
|
var txidStream = new Readable();
|
||||||
|
|
||||||
|
txidStream._read = function() {
|
||||||
|
txidStream.push(data.pop());
|
||||||
|
}
|
||||||
|
|
||||||
|
var createReadStream = sandbox.stub().returns(txidStream);
|
||||||
|
addressService._db = { createKeyStream: createReadStream };
|
||||||
|
|
||||||
|
addressService._getAddressHistory(address, opts, function(err, res) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
expect(getTransaction.calledOnce).to.be.true;
|
||||||
|
expect(res).to.deep.equal([{}]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#AddressSummary', function() {
|
||||||
|
|
||||||
|
it('should get the address summary', function(done) {
|
||||||
|
var encoding = new Encoding(new Buffer('0001', 'hex'));
|
||||||
|
addressService._encoding = encoding;
|
||||||
|
var address = 'a';
|
||||||
|
var txid = tx.txid();
|
||||||
|
var data = [ null, encoding.encodeAddressIndexKey(address, 123, txid, 1, 0) ];
|
||||||
|
var inputValues = [120, 0, 120, 120];
|
||||||
|
tx.__inputValues = inputValues;
|
||||||
|
var getTransaction = sandbox.stub().callsArgWith(2, null, tx);
|
||||||
|
addressService._tx = { getTransaction: getTransaction };
|
||||||
|
addressService._p2p = { getBestHeight: function() { return 150; } };
|
||||||
|
|
||||||
|
var txidStream = new Readable();
|
||||||
|
|
||||||
|
txidStream._read = function() {
|
||||||
|
txidStream.push(data.pop());
|
||||||
|
}
|
||||||
|
|
||||||
|
var createReadStream = sandbox.stub().returns(txidStream);
|
||||||
|
addressService._db = { createKeyStream: createReadStream };
|
||||||
|
|
||||||
|
addressService.getAddressSummary(address, {}, function(err, res) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
expect(getTransaction.calledOnce).to.be.true;
|
||||||
|
expect(res).to.deep.equal({ addrStr: 'a',
|
||||||
|
balance: 0.01139033,
|
||||||
|
balanceSat: 1139033,
|
||||||
|
totalReceived: 0.01139033,
|
||||||
|
totalReceivedSat: 1139033,
|
||||||
|
totalSent: 0,
|
||||||
|
totalSentSat: 0,
|
||||||
|
unconfirmedBalance: 0,
|
||||||
|
unconfirmedBalanceSat: 0,
|
||||||
|
unconfirmedTxApperances: 0,
|
||||||
|
txApperances: 1,
|
||||||
|
transactions: [ '25e28f9fb0ada5353b7d98d85af5524b2f8df5b0b0e2d188f05968bceca603eb' ]
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
describe('#getAddressUnspentOutputs', function() {
|
||||||
|
it('should get address utxos', function(done) {
|
||||||
|
|
||||||
|
var encoding = new Encoding(new Buffer('0001', 'hex'));
|
||||||
|
addressService._encoding = encoding;
|
||||||
|
|
||||||
|
var address = 'a';
|
||||||
|
var txid = tx.txid();
|
||||||
|
var ts = Math.floor(new Date('2019-01-01').getTime() / 1000);
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
key: encoding.encodeUtxoIndexKey(address, txid, 1),
|
||||||
|
value: encoding.encodeUtxoIndexValue(123, 120000, ts, tx.outputs[1].script.raw)
|
||||||
|
};
|
||||||
|
|
||||||
|
addressService._p2p = { getBestHeight: function() { return 150; } };
|
||||||
|
|
||||||
|
var txidStream = new EventEmitter();
|
||||||
|
|
||||||
|
var createReadStream = sandbox.stub().returns(txidStream);
|
||||||
|
addressService._db = { createReadStream: createReadStream };
|
||||||
|
|
||||||
|
addressService.getAddressUnspentOutputs(address, {}, function(err, res) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
expect(res[0]).to.deep.equal({
|
||||||
|
address: "a",
|
||||||
|
amount: 0.0012,
|
||||||
|
confirmations: 27,
|
||||||
|
confirmationsFromCache: true,
|
||||||
|
satoshis: 120000,
|
||||||
|
scriptPubKey: "76a91449f8c749a9960dc29b5cbe7d2397cea7d26611bb88ac",
|
||||||
|
ts: 1546300800,
|
||||||
|
txid: "25e28f9fb0ada5353b7d98d85af5524b2f8df5b0b0e2d188f05968bceca603eb",
|
||||||
|
vout: 1
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
txidStream.emit('data', data);
|
||||||
|
txidStream.emit('end');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#_Rrorg', function() {
|
||||||
|
it('should reorg', function() {
|
||||||
|
var getAddressString = sandbox.stub().returns('a');
|
||||||
|
var encodeTip = sandbox.stub().returns({ key: 'key', value: 'value' });
|
||||||
|
var batch = sandbox.stub();
|
||||||
|
addressService._tip = { height: 100 };
|
||||||
|
addressService._db = { batch: batch };
|
||||||
|
utils.getAddressString = getAddressString;
|
||||||
|
utils.encodeTip = encodeTip;
|
||||||
|
var common = { height: 90 };
|
||||||
|
var oldList = [];
|
||||||
|
var newList = [];
|
||||||
|
addressService._onReorg(oldList, newList, common);
|
||||||
|
expect(batch.calledOnce).to.be.true;
|
||||||
|
expect(getAddressString.calledOnce).to.be.true;
|
||||||
|
expect(encodeTip.calledOnce).to.be.true;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user