Merge pull request #53 from pnagurny/feature/get-txs-address
getAddressHistory
This commit is contained in:
commit
47219a745a
@ -304,4 +304,16 @@ describe('Daemon Binding Functionality', function() {
|
||||
|
||||
});
|
||||
|
||||
describe('get transaction with block info', function() {
|
||||
it('should include tx buffer, height and timestamp', function(done) {
|
||||
bitcoind.getTransactionWithBlockInfo(utxo.txid, true, function(err, data) {
|
||||
should.not.exist(err);
|
||||
data.height.should.equal(151);
|
||||
should.exist(data.timestamp);
|
||||
should.exist(data.buffer);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@ -362,6 +362,10 @@ Daemon.prototype.getTransactionWithBlock = function(txid, blockhash, callback) {
|
||||
});
|
||||
};
|
||||
|
||||
Daemon.prototype.getTransactionWithBlockInfo = function(txid, queryMempool, callback) {
|
||||
return bitcoindjs.getTransactionWithBlockInfo(txid, queryMempool, callback);
|
||||
};
|
||||
|
||||
Daemon.prototype.getMempoolOutputs = function(address) {
|
||||
return bitcoindjs.getMempoolOutputs(address);
|
||||
};
|
||||
|
||||
14
lib/db.js
14
lib/db.js
@ -80,6 +80,20 @@ DB.prototype.getTransaction = function(txid, queryMempool, callback) {
|
||||
});
|
||||
};
|
||||
|
||||
DB.prototype.getTransactionWithBlockInfo = function(txid, queryMempool, callback) {
|
||||
this.bitcoind.getTransactionWithBlockInfo(txid, queryMempool, function(err, obj) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var tx = Transaction().fromBuffer(obj.buffer);
|
||||
tx.__height = obj.height;
|
||||
tx.__timestamp = obj.timestamp;
|
||||
|
||||
callback(null, tx);
|
||||
});
|
||||
}
|
||||
|
||||
DB.prototype.validateBlockData = function(block, callback) {
|
||||
// bitcoind does the validation
|
||||
setImmediate(callback);
|
||||
|
||||
@ -5,6 +5,7 @@ var inherits = require('util').inherits;
|
||||
var async = require('async');
|
||||
var chainlib = require('chainlib');
|
||||
var log = chainlib.log;
|
||||
var levelup = chainlib.deps.levelup;
|
||||
var errors = chainlib.errors;
|
||||
var bitcore = require('bitcore');
|
||||
var $ = bitcore.util.preconditions;
|
||||
@ -23,7 +24,8 @@ var AddressModule = function(options) {
|
||||
inherits(AddressModule, BaseModule);
|
||||
|
||||
AddressModule.PREFIXES = {
|
||||
OUTPUTS: 'outs'
|
||||
OUTPUTS: 'outs',
|
||||
SPENTS: 'sp'
|
||||
};
|
||||
|
||||
AddressModule.prototype.getAPIMethods = function() {
|
||||
@ -31,7 +33,8 @@ AddressModule.prototype.getAPIMethods = function() {
|
||||
['getBalance', this, this.getBalance, 2],
|
||||
['getOutputs', this, this.getOutputs, 2],
|
||||
['getUnspentOutputs', this, this.getUnspentOutputs, 2],
|
||||
['isSpent', this, this.isSpent, 2]
|
||||
['isSpent', this, this.isSpent, 2],
|
||||
['getAddressHistory', this, this.getAddressHistory, 2]
|
||||
];
|
||||
};
|
||||
|
||||
@ -116,6 +119,14 @@ AddressModule.prototype.blockHandler = function(block, addOutput, callback) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for(var j = 0; j < inputs.length; j++) {
|
||||
var input = inputs[j].toObject();
|
||||
operations.push({
|
||||
type: action,
|
||||
key: [AddressModule.PREFIXES.SPENTS, input.prevTxId, input.outputIndex].join('-'),
|
||||
value: [txid, j].join(':')
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setImmediate(function() {
|
||||
@ -281,4 +292,115 @@ AddressModule.prototype.isSpent = function(output, queryMempool, callback) {
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = AddressModule;
|
||||
AddressModule.prototype.getSpendInfoForOutput = function(txid, outputIndex, callback) {
|
||||
var self = this;
|
||||
|
||||
var key = [AddressModule.PREFIXES.SPENTS, txid, outputIndex].join('-');
|
||||
this.db.store.get(key, function(err, value) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
value = value.split(':');
|
||||
|
||||
var info = {
|
||||
txid: value[0],
|
||||
inputIndex: value[1]
|
||||
};
|
||||
|
||||
callback(null, info);
|
||||
});
|
||||
};
|
||||
|
||||
AddressModule.prototype.getAddressHistory = function(address, queryMempool, callback) {
|
||||
var self = this;
|
||||
|
||||
var txinfos = {};
|
||||
|
||||
function getTransactionInfo(txid, callback) {
|
||||
if(txinfos[txid]) {
|
||||
return callback(null, txinfos[txid]);
|
||||
}
|
||||
|
||||
self.db.getTransactionWithBlockInfo(txid, queryMempool, function(err, transaction) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
transaction.populateInputs(self.db, [], function(err) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
txinfos[transaction.hash] = {
|
||||
satoshis: 0,
|
||||
height: transaction.__height,
|
||||
timestamp: transaction.__timestamp,
|
||||
outputIndexes: [],
|
||||
inputIndexes: [],
|
||||
transaction: transaction
|
||||
};
|
||||
|
||||
callback(null, txinfos[transaction.hash]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
this.getOutputs(address, queryMempool, function(err, outputs) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
async.eachSeries(
|
||||
outputs,
|
||||
function(output, next) {
|
||||
getTransactionInfo(output.txid, function(err, txinfo) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
txinfo.outputIndexes.push(output.outputIndex);
|
||||
txinfo.satoshis += output.satoshis;
|
||||
|
||||
self.getSpendInfoForOutput(output.txid, output.outputIndex, function(err, spendInfo) {
|
||||
if(err instanceof levelup.errors.NotFoundError) {
|
||||
return next();
|
||||
} else if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
getTransactionInfo(spendInfo.txid, function(err, txinfo) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
txinfo.inputIndexes.push(spendInfo.inputIndex);
|
||||
txinfo.satoshis -= txinfo.transaction.inputs[spendInfo.inputIndex].output.satoshis;
|
||||
next();
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
function(err) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
// convert to array
|
||||
var history = [];
|
||||
for(var txid in txinfos) {
|
||||
history.push(txinfos[txid]);
|
||||
}
|
||||
|
||||
// sort by height
|
||||
history.sort(function(a, b) {
|
||||
return a.height > b.height;
|
||||
});
|
||||
|
||||
callback(null, history);
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = AddressModule;
|
||||
@ -54,6 +54,10 @@ Transaction.prototype._validateInputs = function(db, poolTransactions, callback)
|
||||
Transaction.prototype.populateInputs = function(db, poolTransactions, callback) {
|
||||
var self = this;
|
||||
|
||||
if(this.isCoinbase()) {
|
||||
return setImmediate(callback);
|
||||
}
|
||||
|
||||
async.each(
|
||||
this.inputs,
|
||||
function(input, next) {
|
||||
@ -68,7 +72,7 @@ Transaction.prototype._populateInput = function(db, input, poolTransactions, cal
|
||||
return callback(new Error('Input is expected to have prevTxId as a buffer'));
|
||||
}
|
||||
var txid = input.prevTxId.toString('hex');
|
||||
db.getTransactionFromDB(txid, function(err, prevTx) {
|
||||
db.getTransaction(txid, false, function(err, prevTx) {
|
||||
if(err instanceof levelup.errors.NotFoundError) {
|
||||
// Check the pool for transaction
|
||||
for(var i = 0; i < poolTransactions.length; i++) {
|
||||
|
||||
@ -74,6 +74,12 @@ async_get_tx(uv_work_t *req);
|
||||
static void
|
||||
async_get_tx_after(uv_work_t *req);
|
||||
|
||||
static void
|
||||
async_get_tx_and_info(uv_work_t *req);
|
||||
|
||||
static void
|
||||
async_get_tx_and_info_after(uv_work_t *req);
|
||||
|
||||
static bool
|
||||
process_messages(CNode* pfrom);
|
||||
|
||||
@ -156,6 +162,8 @@ struct async_tx_data {
|
||||
std::string err_msg;
|
||||
std::string txid;
|
||||
std::string blockhash;
|
||||
uint32_t nTime;
|
||||
int64_t height;
|
||||
bool queryMempool;
|
||||
CTransaction ctx;
|
||||
Eternal<Function> callback;
|
||||
@ -1018,7 +1026,7 @@ async_get_block_after(uv_work_t *req) {
|
||||
|
||||
/**
|
||||
* GetTransaction()
|
||||
* bitcoind.getTransaction(txid, callback)
|
||||
* bitcoind.getTransaction(txid, queryMempool, callback)
|
||||
* Read any transaction from disk asynchronously.
|
||||
*/
|
||||
|
||||
@ -1030,7 +1038,7 @@ NAN_METHOD(GetTransaction) {
|
||||
|| !args[1]->IsBoolean()
|
||||
|| !args[2]->IsFunction()) {
|
||||
return NanThrowError(
|
||||
"Usage: bitcoindjs.getTransaction(txid, callback)");
|
||||
"Usage: daemon.getTransaction(txid, queryMempool, callback)");
|
||||
}
|
||||
|
||||
String::Utf8Value txid_(args[0]->ToString());
|
||||
@ -1145,6 +1153,154 @@ async_get_tx_after(uv_work_t *req) {
|
||||
delete req;
|
||||
}
|
||||
|
||||
/**
|
||||
* GetTransactionWithBlockInfo()
|
||||
* bitcoind.getTransactionWithBlockInfo(txid, queryMempool, callback)
|
||||
* Read any transaction from disk asynchronously with block timestamp and height.
|
||||
*/
|
||||
|
||||
NAN_METHOD(GetTransactionWithBlockInfo) {
|
||||
Isolate* isolate = Isolate::GetCurrent();
|
||||
HandleScope scope(isolate);
|
||||
if (args.Length() < 3
|
||||
|| !args[0]->IsString()
|
||||
|| !args[1]->IsBoolean()
|
||||
|| !args[2]->IsFunction()) {
|
||||
return NanThrowError(
|
||||
"Usage: bitcoindjs.getTransactionWithBlockInfo(txid, queryMempool, callback)");
|
||||
}
|
||||
|
||||
String::Utf8Value txid_(args[0]->ToString());
|
||||
bool queryMempool = args[1]->BooleanValue();
|
||||
Local<Function> callback = Local<Function>::Cast(args[2]);
|
||||
|
||||
async_tx_data *data = new async_tx_data();
|
||||
|
||||
data->err_msg = std::string("");
|
||||
data->txid = std::string("");
|
||||
|
||||
std::string txid = std::string(*txid_);
|
||||
|
||||
data->txid = txid;
|
||||
data->queryMempool = queryMempool;
|
||||
Eternal<Function> eternal(isolate, callback);
|
||||
data->callback = eternal;
|
||||
|
||||
uv_work_t *req = new uv_work_t();
|
||||
req->data = data;
|
||||
|
||||
int status = uv_queue_work(uv_default_loop(),
|
||||
req, async_get_tx_and_info,
|
||||
(uv_after_work_cb)async_get_tx_and_info_after);
|
||||
|
||||
assert(status == 0);
|
||||
|
||||
NanReturnValue(Undefined(isolate));
|
||||
}
|
||||
|
||||
static void
|
||||
async_get_tx_and_info(uv_work_t *req) {
|
||||
async_tx_data* data = static_cast<async_tx_data*>(req->data);
|
||||
|
||||
uint256 hash = uint256S(data->txid);
|
||||
uint256 blockHash;
|
||||
CTransaction ctx;
|
||||
|
||||
if (data->queryMempool) {
|
||||
LOCK(mempool.cs);
|
||||
map<uint256, CTxMemPoolEntry>::const_iterator i = mempool.mapTx.find(hash);
|
||||
if (i != mempool.mapTx.end()) {
|
||||
data->ctx = i->second.GetTx();
|
||||
data->nTime = i->second.GetTime();
|
||||
data->height = -1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
CDiskTxPos postx;
|
||||
if (pblocktree->ReadTxIndex(hash, postx)) {
|
||||
|
||||
CAutoFile file(OpenBlockFile(postx, true), SER_DISK, CLIENT_VERSION);
|
||||
|
||||
if (file.IsNull()) {
|
||||
data->err_msg = std::string("%s: OpenBlockFile failed", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
CBlockHeader blockHeader;
|
||||
|
||||
try {
|
||||
// Read header first to get block timestamp and hash
|
||||
file >> blockHeader;
|
||||
blockHash = blockHeader.GetHash();
|
||||
data->nTime = blockHeader.nTime;
|
||||
fseek(file.Get(), postx.nTxOffset, SEEK_CUR);
|
||||
file >> ctx;
|
||||
data->ctx = ctx;
|
||||
} catch (const std::exception& e) {
|
||||
data->err_msg = std::string("Deserialize or I/O error - %s", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
// get block height
|
||||
CBlockIndex* blockIndex;
|
||||
|
||||
if (mapBlockIndex.count(blockHash) == 0) {
|
||||
data->height = -1;
|
||||
} else {
|
||||
blockIndex = mapBlockIndex[blockHash];
|
||||
data->height = blockIndex->nHeight;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
async_get_tx_and_info_after(uv_work_t *req) {
|
||||
Isolate* isolate = Isolate::GetCurrent();
|
||||
HandleScope scope(isolate);
|
||||
async_tx_data* data = static_cast<async_tx_data*>(req->data);
|
||||
|
||||
CTransaction ctx = data->ctx;
|
||||
Local<Function> cb = data->callback.Get(isolate);
|
||||
Local<Object> obj = NanNew<Object>();
|
||||
|
||||
if (data->err_msg != "") {
|
||||
Local<Value> err = Exception::Error(NanNew<String>(data->err_msg));
|
||||
const unsigned argc = 1;
|
||||
Local<Value> argv[argc] = { err };
|
||||
TryCatch try_catch;
|
||||
cb->Call(isolate->GetCurrentContext()->Global(), argc, argv);
|
||||
if (try_catch.HasCaught()) {
|
||||
node::FatalException(try_catch);
|
||||
}
|
||||
} else {
|
||||
|
||||
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ssTx << ctx;
|
||||
std::string stx = ssTx.str();
|
||||
Local<Value> rawNodeBuffer = node::Buffer::New(isolate, stx.c_str(), stx.size());
|
||||
|
||||
obj->Set(NanNew<String>("height"), NanNew<Number>(data->height));
|
||||
obj->Set(NanNew<String>("timestamp"), NanNew<Number>(data->nTime));
|
||||
obj->Set(NanNew<String>("buffer"), rawNodeBuffer);
|
||||
|
||||
const unsigned argc = 2;
|
||||
Local<Value> argv[argc] = {
|
||||
Local<Value>::New(isolate, NanNull()),
|
||||
obj
|
||||
};
|
||||
TryCatch try_catch;
|
||||
cb->Call(isolate->GetCurrentContext()->Global(), argc, argv);
|
||||
if (try_catch.HasCaught()) {
|
||||
node::FatalException(try_catch);
|
||||
}
|
||||
}
|
||||
delete data;
|
||||
delete req;
|
||||
}
|
||||
|
||||
/**
|
||||
* IsSpent()
|
||||
* bitcoindjs.isSpent()
|
||||
@ -1465,6 +1621,7 @@ init(Handle<Object> target) {
|
||||
NODE_SET_METHOD(target, "stopped", IsStopped);
|
||||
NODE_SET_METHOD(target, "getBlock", GetBlock);
|
||||
NODE_SET_METHOD(target, "getTransaction", GetTransaction);
|
||||
NODE_SET_METHOD(target, "getTransactionWithBlockInfo", GetTransactionWithBlockInfo);
|
||||
NODE_SET_METHOD(target, "getInfo", GetInfo);
|
||||
NODE_SET_METHOD(target, "isSpent", IsSpent);
|
||||
NODE_SET_METHOD(target, "getBlockIndex", GetBlockIndex);
|
||||
|
||||
@ -8,6 +8,8 @@ var blockData = require('../data/livenet-345003.json');
|
||||
var bitcore = require('bitcore');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var errors = bitcoindjs.errors;
|
||||
var chainlib = require('chainlib');
|
||||
var levelup = chainlib.deps.levelup;
|
||||
|
||||
describe('AddressModule', function() {
|
||||
|
||||
@ -15,7 +17,7 @@ describe('AddressModule', function() {
|
||||
it('should return the correct methods', function() {
|
||||
var am = new AddressModule({});
|
||||
var methods = am.getAPIMethods();
|
||||
methods.length.should.equal(4);
|
||||
methods.length.should.equal(5);
|
||||
});
|
||||
});
|
||||
|
||||
@ -111,21 +113,35 @@ describe('AddressModule', function() {
|
||||
it('should create the correct operations when updating/adding outputs', function(done) {
|
||||
am.blockHandler({__height: 345003, timestamp: new Date(1424836934000)}, true, function(err, operations) {
|
||||
should.not.exist(err);
|
||||
operations.length.should.equal(11);
|
||||
operations.length.should.equal(81);
|
||||
operations[0].type.should.equal('put');
|
||||
var expected0 = ['outs', key0.address, key0.timestamp, key0.txid, key0.outputIndex].join('-');
|
||||
operations[0].key.should.equal(expected0);
|
||||
operations[0].value.should.equal([value0.satoshis, value0.script, value0.blockHeight].join(':'));
|
||||
operations[3].type.should.equal('put');
|
||||
var expected3 = ['sp', key3.prevTxId, key3.prevOutputIndex].join('-');
|
||||
operations[3].key.should.equal(expected3);
|
||||
operations[3].value.should.equal([value3.txid, value3.inputIndex].join(':'));
|
||||
operations[64].type.should.equal('put');
|
||||
var expected64 = ['outs', key64.address, key64.timestamp, key64.txid, key64.outputIndex].join('-');
|
||||
operations[64].key.should.equal(expected64);
|
||||
operations[64].value.should.equal([value64.satoshis, value64.script, value64.blockHeight].join(':'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should create the correct operations when removing outputs', function(done) {
|
||||
am.blockHandler({__height: 345003, timestamp: new Date(1424836934000)}, false, function(err, operations) {
|
||||
should.not.exist(err);
|
||||
operations.length.should.equal(11);
|
||||
operations.length.should.equal(81);
|
||||
operations[0].type.should.equal('del');
|
||||
operations[0].key.should.equal(['outs', key0.address, key0.timestamp, key0.txid, key0.outputIndex].join('-'));
|
||||
operations[0].value.should.equal([value0.satoshis, value0.script, value0.blockHeight].join(':'));
|
||||
operations[3].type.should.equal('del');
|
||||
operations[3].key.should.equal(['sp', key3.prevTxId, key3.prevOutputIndex].join('-'));
|
||||
operations[3].value.should.equal([value3.txid, value3.inputIndex].join(':'));
|
||||
operations[64].type.should.equal('del');
|
||||
operations[64].key.should.equal(['outs', key64.address, key64.timestamp, key64.txid, key64.outputIndex].join('-'));
|
||||
operations[64].value.should.equal([value64.satoshis, value64.script, value64.blockHeight].join(':'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
@ -481,4 +497,210 @@ describe('AddressModule', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getSpendInfoForOutput', function() {
|
||||
it('should call store.get the right values', function(done) {
|
||||
var db = {
|
||||
store: {
|
||||
get: sinon.stub().callsArgWith(1, null, 'spendtxid:1')
|
||||
}
|
||||
};
|
||||
var am = new AddressModule({db: db});
|
||||
am.getSpendInfoForOutput('txid', 3, function(err, info) {
|
||||
should.not.exist(err);
|
||||
info.txid.should.equal('spendtxid');
|
||||
info.inputIndex.should.equal('1');
|
||||
db.store.get.args[0][0].should.equal('sp-txid-3');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getAddressHistory', function() {
|
||||
var incoming = [
|
||||
{
|
||||
txid: 'tx1',
|
||||
outputIndex: 0,
|
||||
spentTx: 'tx2',
|
||||
inputIndex: 0,
|
||||
height: 1,
|
||||
timestamp: 1438289011844,
|
||||
satoshis: 5000
|
||||
},
|
||||
{
|
||||
txid: 'tx3',
|
||||
outputIndex: 1,
|
||||
height: 3,
|
||||
timestamp: 1438289031844,
|
||||
satoshis: 2000
|
||||
},
|
||||
{
|
||||
txid: 'tx4',
|
||||
outputIndex: 2,
|
||||
spentTx: 'tx5',
|
||||
inputIndex: 1,
|
||||
height: 4,
|
||||
timestamp: 1438289041844,
|
||||
satoshis: 3000
|
||||
},
|
||||
];
|
||||
|
||||
var outgoing = [
|
||||
{
|
||||
txid: 'tx2',
|
||||
height: 2,
|
||||
timestamp: 1438289021844,
|
||||
inputs: [
|
||||
{
|
||||
output: {
|
||||
satoshis: 5000
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
txid: 'tx5',
|
||||
height: 5,
|
||||
timestamp: 1438289051844,
|
||||
inputs: [
|
||||
{},
|
||||
{
|
||||
output: {
|
||||
satoshis: 3000
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
var db = {
|
||||
getTransactionWithBlockInfo: function(txid, queryMempool, callback) {
|
||||
var transaction = {
|
||||
populateInputs: sinon.stub().callsArg(2)
|
||||
};
|
||||
for(var i = 0; i < incoming.length; i++) {
|
||||
if(incoming[i].txid === txid) {
|
||||
if(incoming[i].error) {
|
||||
return callback(new Error(incoming[i].error));
|
||||
}
|
||||
transaction.hash = txid;
|
||||
transaction.__height = incoming[i].height;
|
||||
transaction.__timestamp = incoming[i].timestamp;
|
||||
return callback(null, transaction);
|
||||
}
|
||||
}
|
||||
|
||||
for(var i = 0; i < outgoing.length; i++) {
|
||||
if(outgoing[i].txid === txid) {
|
||||
if(outgoing[i].error) {
|
||||
return callback(new Error(outgoing[i].error));
|
||||
}
|
||||
transaction.hash = txid;
|
||||
transaction.__height = outgoing[i].height;
|
||||
transaction.__timestamp = outgoing[i].timestamp;
|
||||
transaction.inputs = outgoing[i].inputs;
|
||||
return callback(null, transaction);
|
||||
}
|
||||
}
|
||||
callback(new Error('tx ' + txid + ' not found'));
|
||||
}
|
||||
};
|
||||
var am = new AddressModule({db: db});
|
||||
|
||||
am.getOutputs = sinon.stub().callsArgWith(2, null, incoming);
|
||||
am.getSpendInfoForOutput = function(txid, outputIndex, callback) {
|
||||
for(var i = 0; i < incoming.length; i++) {
|
||||
if(incoming[i].txid === txid && incoming[i].outputIndex === outputIndex && incoming[i].spentTx) {
|
||||
if(incoming[i].spendError) {
|
||||
return callback(new Error(incoming[i].spendError));
|
||||
}
|
||||
return callback(null, {
|
||||
txid: incoming[i].spentTx,
|
||||
inputIndex: incoming[i].inputIndex
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
callback(new levelup.errors.NotFoundError());
|
||||
};
|
||||
|
||||
it('should give transaction history for an address', function(done) {
|
||||
am.getAddressHistory('address', true, function(err, history) {
|
||||
should.not.exist(err);
|
||||
history[0].transaction.hash.should.equal('tx1');
|
||||
history[0].satoshis.should.equal(5000);
|
||||
history[0].height.should.equal(1);
|
||||
history[0].timestamp.should.equal(1438289011844);
|
||||
history[1].transaction.hash.should.equal('tx2');
|
||||
history[1].satoshis.should.equal(-5000);
|
||||
history[1].height.should.equal(2);
|
||||
history[1].timestamp.should.equal(1438289021844);
|
||||
history[2].transaction.hash.should.equal('tx3');
|
||||
history[2].satoshis.should.equal(2000);
|
||||
history[2].height.should.equal(3);
|
||||
history[2].timestamp.should.equal(1438289031844);
|
||||
history[3].transaction.hash.should.equal('tx4');
|
||||
history[3].satoshis.should.equal(3000);
|
||||
history[3].height.should.equal(4);
|
||||
history[3].timestamp.should.equal(1438289041844);
|
||||
history[4].transaction.hash.should.equal('tx5');
|
||||
history[4].satoshis.should.equal(-3000);
|
||||
history[4].height.should.equal(5);
|
||||
history[4].timestamp.should.equal(1438289051844);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should give an error if the second getTransactionInfo gives an error', function(done) {
|
||||
outgoing[0].error = 'txinfo2err';
|
||||
am.getAddressHistory('address', true, function(err, history) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('txinfo2err');
|
||||
outgoing[0].error = null;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should give an error if getSpendInfoForOutput gives an error', function(done) {
|
||||
incoming[0].spendError = 'spenderr';
|
||||
am.getAddressHistory('address', true, function(err, history) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('spenderr');
|
||||
incoming[0].spendError = null;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should give an error if the first getTransactionInfo gives an error', function(done) {
|
||||
incoming[1].error = 'txinfo1err';
|
||||
am.getAddressHistory('address', true, function(err, history) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('txinfo1err');
|
||||
incoming[1].error = null;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should give an error if populateInputs gives an error', function(done) {
|
||||
var populateStub = sinon.stub().callsArgWith(2, new Error('populateerr'));
|
||||
sinon.stub(db, 'getTransactionWithBlockInfo').callsArgWith(2, null, {
|
||||
populateInputs: populateStub
|
||||
});
|
||||
am.getAddressHistory('address', true, function(err, history) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('populateerr');
|
||||
db.getTransactionWithBlockInfo.restore();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should give an error if getOutputs gives an error', function(done) {
|
||||
am.getOutputs = sinon.stub().callsArgWith(2, new Error('getoutputserr'));
|
||||
am.getAddressHistory('address', true, function(err, history) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('getoutputserr');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@ -106,6 +106,7 @@ describe('Bitcoin Transaction', function() {
|
||||
describe('#populateInputs', function() {
|
||||
it('will call _populateInput with transactions', function() {
|
||||
var tx = new Transaction();
|
||||
tx.isCoinbase = sinon.stub().returns(false);
|
||||
tx._populateInput = sinon.stub().callsArg(3);
|
||||
tx.inputs = ['input'];
|
||||
var transactions = [];
|
||||
@ -138,7 +139,7 @@ describe('Bitcoin Transaction', function() {
|
||||
it('if an error happened it should pass it along', function(done) {
|
||||
var tx = new Transaction();
|
||||
var db = {
|
||||
getTransactionFromDB: sinon.stub().callsArgWith(1, new Error('error'))
|
||||
getTransaction: sinon.stub().callsArgWith(2, new Error('error'))
|
||||
};
|
||||
tx._populateInput(db, input, [], function(err) {
|
||||
should.exist(err);
|
||||
@ -149,7 +150,7 @@ describe('Bitcoin Transaction', function() {
|
||||
it('should return an error if the transaction for the input does not exist', function(done) {
|
||||
var tx = new Transaction();
|
||||
var db = {
|
||||
getTransactionFromDB: sinon.stub().callsArgWith(1, new levelup.errors.NotFoundError())
|
||||
getTransaction: sinon.stub().callsArgWith(2, new levelup.errors.NotFoundError())
|
||||
};
|
||||
tx._populateInput(db, input, [], function(err) {
|
||||
should.exist(err);
|
||||
@ -160,7 +161,7 @@ describe('Bitcoin Transaction', function() {
|
||||
it('should look through poolTransactions if database does not have transaction', function(done) {
|
||||
var tx = new Transaction();
|
||||
var db = {
|
||||
getTransactionFromDB: sinon.stub().callsArgWith(1, new levelup.errors.NotFoundError())
|
||||
getTransaction: sinon.stub().callsArgWith(2, new levelup.errors.NotFoundError())
|
||||
};
|
||||
var transactions = [
|
||||
{
|
||||
@ -179,7 +180,7 @@ describe('Bitcoin Transaction', function() {
|
||||
prevTx.outputs = ['output'];
|
||||
var tx = new Transaction();
|
||||
var db = {
|
||||
getTransactionFromDB: sinon.stub().callsArgWith(1, null, prevTx)
|
||||
getTransaction: sinon.stub().callsArgWith(2, null, prevTx)
|
||||
};
|
||||
tx._populateInput(db, input, [], function(err) {
|
||||
should.not.exist(err);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user