Merge pull request #43 from eordano/address

Address service
This commit is contained in:
Manuel Aráoz 2015-03-20 13:30:52 -03:00
commit fc8f53372a
8 changed files with 499 additions and 55 deletions

View File

@ -6,8 +6,8 @@ BitcoreNode:
Reporter: simple # none, simple, matrix
LevelUp: ./db
RPC:
username: username
password: password
user: username
pass: password
protocol: http
host: 127.0.0.1
port: 8332

142
lib/services/address.js Normal file
View File

@ -0,0 +1,142 @@
'use strict';
var Promise = require('bluebird');
var bitcore = require('bitcore');
var TransactionService = require('./transaction');
var _ = bitcore.deps._;
var NULLTXHASH = bitcore.util.buffer.emptyBuffer(32).toString('hex');
var LASTTXHASH = bitcore.util.buffer.fill(bitcore.util.buffer.emptyBuffer(32), -1).toString('hex');
var MAXOUTPUT = 4294967295;
function AddressService(opts) {
opts = _.extend({}, opts);
this.transactionService = opts.transactionService;
this.blockService = opts.blockService;
this.database = opts.database || Promise.promisifyAll(new LevelUp(config.get('LevelUp')));
this.rpc = opts.rpc || Promise.promisifyAll(new RPC(config.get('RPC')));
}
AddressService.prototype.getSummary = function(address, confirmations) {
var self = this;
var tip, allOutputs, spent;
return Promise.try(function() {
return self.blockService.getLatest();
}).then(function(latest) {
tip = latest;
return self.getAllOutputs(address);
}).then(function(outputs) {
allOutputs = outputs;
return self.getSpent(address);
}).then(function(spent) {
return self.buildAddressSummary(address, tip, allOutputs, spent, confirmations);
});
};
AddressService.processOutput = function(data) {
var elements = data.key.split('-');
var output = _.extend(JSON.parse(data.value), {
address: elements[1],
txId: elements[2],
outputIndex: elements[3]
});
return output;
};
var retrieveOutputs = function(indexFunction, processElement) {
return function(address) {
var results = [];
var self = this;
return new Promise(function(resolve, reject) {
self.database.createReadStream({
gte: indexFunction(address, NULLTXHASH, 0),
lte: indexFunction(address, LASTTXHASH, MAXOUTPUT)
}).on('data', function(element) {
results.push(processElement(element));
}).on('error', reject).on('end', function() {
return resolve(results);
});
});
};
};
AddressService.prototype.getAllOutputs = retrieveOutputs(
TransactionService.Index.getOutputsForAddress,
function(e) {
return AddressService.processOutput(e);
}
);
AddressService.prototype.getSpent = retrieveOutputs(
TransactionService.Index.getSpentOutputsForAddress,
function(element) {
return JSON.parse(element.value);
}
);
AddressService.prototype.buildAddressSummary = function(address, tip, allOutputs, spent, confirmations) {
var result = {};
var transactionsAppended = {};
confirmations = confirmations || 6;
result.address = address.toString();
result.transactions = [];
result.confirmed = {
balance: 0,
sent: 0,
received: 0
};
result.unconfirmed = {
balance: 0,
sent: 0,
received: 0
};
var outputValues = {};
_.each(allOutputs, function(output) {
var value = output.satoshis;
outputValues[output.txId + '-' + output.outputIndex] = value;
result.unconfirmed.balance += value;
result.unconfirmed.received += value;
if (tip.height - output.heightConfirmed + 1 >= confirmations) {
result.confirmed.balance += value;
result.confirmed.received += value;
}
if (!transactionsAppended[output.txId]) {
transactionsAppended[output.txId] = true;
result.transactions.push(output.txId);
}
});
_.each(spent, function(output) {
var value = outputValues[output.spendInput.prevTxId + '-' + output.spendInput.outputIndex];
if (!transactionsAppended[output.spentTx]) {
transactionsAppended[output.spentTx] = true;
result.transactions.push(output.spentTx);
}
result.unconfirmed.balance -= value;
result.unconfirmed.sent += value;
if (tip.height - output.heightSpent + 1 >= confirmations) {
result.confirmed.balance -= value;
result.confirmed.sent += value;
}
});
return result;
};
module.exports = AddressService;

View File

@ -63,11 +63,20 @@ function BlockService (opts) {
BlockService.prototype.writeLock = function() {
var self = this;
return Promise.try(function() {
// TODO
return new Promise(function(resolve, reject) {
if (self.lock) {
return reject();
} else {
self.lock = true;
return resolve();
}
});
};
BlockService.prototype.unlock = function() {
this.lock = false;
};
/**
* Transforms data as received from an RPC result structure for `getblock`,
* plus a list of transactions, and build a block based on thosė.
@ -88,23 +97,26 @@ BlockService.blockRPCtoBitcore = function(blockData, transactions) {
$.checkArgument(_.all(transactions, function(transaction) {
return transaction instanceof bitcore.Transaction;
}), 'All transactions must be instances of bitcore.Transaction');
return new bitcore.Block({
var block = new bitcore.Block({
header: new bitcore.BlockHeader({
version: blockData.version,
prevHash: bitcore.util.buffer.reverse(
new bitcore.deps.Buffer(blockData.previousblockhash, 'hex')
),
prevHash: blockData.previousblockhash ?
bitcore.util.buffer.reverse(
new bitcore.deps.Buffer(blockData.previousblockhash, 'hex')
) : bitcore.util.buffer.emptyBuffer(32),
time: blockData.time,
nonce: blockData.nonce,
bits: new bitcore.deps.bnjs(
new bitcore.deps.Buffer(blockData.bits, 'hex')
),
merkleRoot: bitcore.util.buffer.reverse(
new bitcore.deps.Buffer(blockData.merkleRoot, 'hex')
new bitcore.deps.Buffer(blockData.merkleroot, 'hex')
)
}),
transactions: transactions
});
block.height = blockData.height;
return block;
};
/**
@ -114,7 +126,7 @@ BlockService.blockRPCtoBitcore = function(blockData, transactions) {
* @return {Promise} a promise that will always be rejected
*/
var blockNotFound = function(err) {
console.log(err);
console.log(err, err.stack);
return Promise.reject(new BitcoreNode.errors.Blocks.NotFound());
};
@ -168,7 +180,7 @@ BlockService.prototype.getBlockByHeight = function(height) {
return Promise.try(function() {
return this.rpc.getBlockHash(height);
return self.rpc.getBlockHash(height);
}).then(function(blockHash) {
@ -210,11 +222,11 @@ BlockService.prototype._confirmBlock = function(block) {
var ops = [];
this.writeLock().then(
return this.writeLock().then(function() {
self._setNextBlock.bind(self, ops, block.header.prevHash, block)
return self._setNextBlock(ops, block.header.prevHash, block);
).then(function() {
}).then(function() {
if (block.header.prevHash.toString('hex') !== NULLBLOCKHASH) {
return self.getBlock(block.header.prevHash);
@ -226,21 +238,23 @@ BlockService.prototype._confirmBlock = function(block) {
return self._setBlockHeight(ops, block, parent.height + 1);
}).then(
}).then(function() {
self._setBlockByTs.bind(self, ops, block)
return self._setBlockByTs(ops, block);
).then(function() {
}).then(function() {
return Promise.all(block.transactions.map(function(transaction) {
return self.transactionService._confirmTransaction(ops, block, transaction);
}));
}).then(
}).then(function() {
self.database.batchAsync.bind(self, ops)
return self.database.batchAsync(ops)
).then(this.unlock);
}).then(function() {
return self.unlock();
});
};
BlockService.prototype._setNextBlock = function(ops, prevBlockHash, block) {

View File

@ -0,0 +1,6 @@
module.exports = ''
+ '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff'
+ '4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72'
+ '206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff'
+ '0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f'
+ '61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000';

View File

@ -25,6 +25,7 @@ var _ = bitcore.deps._;
var $ = bitcore.util.preconditions;
var NULLTXHASH = bitcore.util.buffer.emptyBuffer(32).toString('hex');
var GENESISTX = '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b'
var helper = function(name) {
return function(txId, output) {
@ -80,6 +81,7 @@ function TransactionService (opts) {
this.database = opts.database || Promise.promisifyAll(new LevelUp(config.get('LevelUp')));
this.rpc = opts.rpc || Promise.promisifyAll(new RPC(config.get('RPC')));
}
TransactionService.Index = Index;
TransactionService.transactionRPCtoBitcore = function(rpcResponse) {
if (rpcResponse.error) {
@ -92,6 +94,10 @@ TransactionService.prototype.getTransaction = function(transactionId) {
var self = this;
if (transactionId === GENESISTX) {
return new bitcore.Transaction(require('./data/genesistx'));
}
return Promise.try(function() {
return self.rpc.getRawTransactionAsync(transactionId);
}).then(function(rawTransaction) {
@ -104,7 +110,7 @@ TransactionService.prototype._confirmOutput = function(ops, block, transaction)
ops.push({
type: 'put',
key: Index.getOutput(transaction.id, index),
value: output.toObject()
value: output.toJSON()
});
var address;
// TODO: Move this logic to bitcore
@ -118,7 +124,9 @@ TransactionService.prototype._confirmOutput = function(ops, block, transaction)
ops.push({
type: 'put',
key: Index.getOutputsForAddress(address, transaction.id, index),
value: output.toObject()
value: JSON.stringify(_.extend(output.toObject(), {
heightConfirmed: block.height
}))
});
}
};
@ -133,26 +141,28 @@ TransactionService.prototype._confirmInput = function(ops, block, transaction) {
ops.push({
type: 'put',
key: Index.getOutput(transaction.id, index),
value: _.extend(input.toObject(), {
value: JSON.stringify(_.extend(input.toObject(), {
heightConfirmed: block.height
})
}))
});
var script = input.script;
if (!(script.isPublicKeyHashIn() || script.isPublicKeyIn() || script.isScriptHashIn())) {
return Promise.resolve();
return;
}
return self._getAddressForInput(input).then(function(address) {
return Promise.try(function() {
return self._getAddressForInput(input)
}).then(function(address) {
if (address) {
ops.push({
type: 'put',
key: Index.getSpentOutputsForAddress(address, transaction.id, index),
value: {
value: JSON.stringify({
heightSpent: block.height,
spentTx: transaction.id,
spentTxInputIndex: index,
spendInput: input.toObject()
}
})
});
}
});
@ -165,40 +175,37 @@ TransactionService.prototype._getAddressForInput = function(input) {
if (script.isPublicKeyHashIn()) {
var hash = bitcore.crypto.Hash.sha256ripemd160(script.chunks[0].buf);
return Promise.resolve(new bitcore.Address(
return new bitcore.Address(
hash, bitcore.Networks.defaultNetwork, bitcore.Address.PayToPublicKeyHash
));
);
} else if (script.isPublicKeyIn()) {
return self.getTransaction(input.prevTxId.toString('hex')).then(function(transaction) {
var outputScript = transaction.outputs[input.outputIndex].script;
if (outputScript.isPublicKeyOut()) {
return Promise.resolve(new bitcore.Address(
return new bitcore.Address(
bitcore.crypto.Hash.sha256ripemd160(outputScript.chunks[0].buf),
bitcore.Networks.defaultNetwork, bitcore.Address.PayToPublicKeyHash
));
} else {
return Promise.resolve(undefined);
);
}
return;
});
} else {
return Promise.resolve(new bitcore.Script(script.chunks[script.chunks.length - 1]).toAddress());
return new bitcore.Script(script.chunks[script.chunks.length - 1]).toAddress();
}
};
TransactionService.prototype._confirmTransaction = function(ops, block, transaction) {
var self = this;
return Promise.try(function() {
ops.push({
type: 'put',
key: Index.getBlockForTransaction(transaction),
value: block.id
});
return Promise.all(
_.each(transaction.outputs, self._confirmOutput(ops, block, transaction))
.concat(
_.each(transaction.inputs, self._confirmInput(ops, block, transaction))
));
ops.push({
type: 'put',
key: Index.getBlockForTransaction(transaction),
value: block.id
});
return Promise.all(
_.map(transaction.outputs, self._confirmOutput(ops, block, transaction))
.concat(
_.map(transaction.inputs, self._confirmInput(ops, block, transaction))
));
};
module.exports = TransactionService;

264
test/services/address.js Normal file
View File

@ -0,0 +1,264 @@
'use strict';
var sinon = require('sinon');
var should = require('chai').should();
var events = require('events');
var Promise = require('bluebird');
var bitcore = require('bitcore');
var _ = bitcore.deps._;
var AddressService = require('../../lib/services/address');
describe('AddressService', function() {
var database, rpc, blockService, transactionService, service;
function initialize() {
database = {};
rpc = {};
blockService = {};
transactionService = {};
service = new AddressService({
database: database,
transactionService: transactionService,
blockService: blockService,
rpc: rpc
});
}
it('initializes correctly', function() {
initialize();
should.exist(service);
});
var thenCaller = {
then: function(arg) {
return arg();
}
};
describe('getSummary', function() {
beforeEach(initialize);
it('calls internal functions as expected', function(done) {
service.blockService = { getLatest: sinon.mock() };
service.getAllOutputs = sinon.mock();
service.getSpent = sinon.mock();
service.buildAddressSummary = sinon.mock();
service.blockService.getLatest.onFirstCall().returns(thenCaller);
service.getAllOutputs.onFirstCall().returns(thenCaller);
service.getSpent.onFirstCall().returns(thenCaller);
service.buildAddressSummary.onFirstCall().returns(thenCaller);
var address = 'address';
var confirmations = 100;
var promise = service.getSummary(address, confirmations);
promise.then(function() {
service.blockService.getLatest.calledOnce.should.equal(true);
service.getAllOutputs.calledOnce.should.equal(true);
service.getSpent.calledOnce.should.equal(true);
service.buildAddressSummary.calledOnce.should.equal(true);
done();
});
});
it('processOutput works as expected', function() {
AddressService.processOutput({
key: 'txas-A-B-C',
value: '{"a": "b"}'
}).should.deep.equal({
address: 'A',
txId: 'B',
outputIndex: 'C',
a: 'b'
});
});
it('getAllOutputs rejects promise on error', function(done) {
var dataCall = new events.EventEmitter();
var address = '12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S';
service.database.createReadStream = sinon.mock();
service.database.createReadStream.onFirstCall().returns(dataCall);
service.getAllOutputs(address).catch(done);
dataCall.emit('error');
});
it('getSpent rejects promise on error', function(done) {
var address = '12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S';
var dataCall = new events.EventEmitter();
service.database.createReadStream = sinon.mock();
service.database.createReadStream.onFirstCall().returns(dataCall);
service.getSpent(address).catch(done);
dataCall.emit('error');
});
it('getAllOutputs calls the expected functions', function(done) {
service.database.createReadStream = sinon.mock();
var dataCall = new events.EventEmitter();
service.database.createReadStream.onFirstCall().returns(dataCall);
AddressService.processOutput = sinon.stub(AddressService, 'processOutput');
AddressService.processOutput.onFirstCall().returns('processed');
var element = {key: 'key', value: 'value'};
var address = '12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S';
service.getAllOutputs(address).then(function(arg) {
service.database.createReadStream.firstCall.args[0].should.deep.equal(
{
gte: 'txa-12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S-'
+ '0000000000000000000000000000000000000000000000000000000000000000-0',
lte: 'txa-12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S-'
+ 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff-4294967295'
}
);
AddressService.processOutput.firstCall.args[0].should.equal(element);
AddressService.processOutput.reset();
arg[0].should.equal('processed');
done();
});
dataCall.emit('data', element);
dataCall.emit('end');
});
it('getSpent calls the expected functions', function(done) {
service.database.createReadStream = sinon.mock();
var dataCall = new events.EventEmitter();
service.database.createReadStream.onFirstCall().returns(dataCall);
var element = {key: 'key', value: JSON.stringify({a: 'b'})};
var address = '12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S';
service.getSpent(address).then(function(arg) {
service.database.createReadStream.firstCall.args[0].should.deep.equal(
{
gte: 'txas-12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S-'
+ '0000000000000000000000000000000000000000000000000000000000000000-0',
lte: 'txas-12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S-'
+ 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff-4294967295'
}
);
arg[0].should.deep.equal({a: 'b'});
done();
});
dataCall.emit('data', element);
dataCall.emit('end');
});
});
describe('buildAddressSummary', function() {
beforeEach(initialize);
var address = new bitcore.Address('12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S');
var tip = {
height: 10
};
var allOutputs = [
{
satoshis: 10,
txId: 'A',
outputIndex: 1,
heightConfirmed: 1
}
];
it('calculates balance correctly for confirmed balance', function() {
var allOutputs = [ { satoshis: 10, txId: 'A', outputIndex: 1, heightConfirmed: 1 } ];
var spendOutputs = [];
service.buildAddressSummary(address, tip, allOutputs, spendOutputs).should.deep.equal({
address: address.toString(),
transactions: ['A'],
confirmed: { balance: 10, sent: 0, received: 10 },
unconfirmed: { balance: 10, sent: 0, received: 10 }
});
});
it('calculates balance correctly for unconfirmed balance', function() {
var allOutputs = [
{ satoshis: 20, txId: 'B', outputIndex: 1, heightConfirmed: 10 }
];
var spendOutputs = [ ];
service.buildAddressSummary(address, tip, allOutputs, spendOutputs).should.deep.equal({
address: address.toString(),
transactions: ['B'],
confirmed: { balance: 0, sent: 0, received: 0 },
unconfirmed: { balance: 20, sent: 0, received: 20 }
});
});
it('works with multiple transactions', function() {
var allOutputs = [
{ satoshis: 10, txId: 'A', outputIndex: 1, heightConfirmed: 1 },
{ satoshis: 20, txId: 'B', outputIndex: 1, heightConfirmed: 10 }
];
var spendOutputs = [
{ spendInput: { prevTxId: 'A', outputIndex: 1 }, spentTx: 'A', heightSpent: 10 }
];
service.buildAddressSummary(address, tip, allOutputs, spendOutputs).should.deep.equal({
address: address.toString(),
transactions: ['A', 'B'],
confirmed: { balance: 10, sent: 0, received: 10 },
unconfirmed: { balance: 20, sent: 10, received: 30 }
});
});
it('works with a medium amount of transactions', function() {
var allOutputs = [
{ satoshis: 10, txId: 'A', outputIndex: 1, heightConfirmed: 1 },
{ satoshis: 20, txId: 'B', outputIndex: 1, heightConfirmed: 5 },
{ satoshis: 30, txId: 'C', outputIndex: 1, heightConfirmed: 10 }
];
var spendOutputs = [
{ spendInput: { prevTxId: 'A', outputIndex: 1 }, spentTx: 'D', heightSpent: 10 }
];
service.buildAddressSummary(address, tip, allOutputs, spendOutputs).should.deep.equal({
address: address.toString(),
transactions: ['A', 'B', 'C', 'D'],
confirmed: { balance: 30, sent: 0, received: 30 },
unconfirmed: { balance: 50, sent: 10, received: 60 }
});
});
it('works with a transaction that includes twice the same address', function() {
var allOutputs = [
{ satoshis: 10, txId: 'A', outputIndex: 0, heightConfirmed: 1 },
{ satoshis: 10, txId: 'A', outputIndex: 1, heightConfirmed: 1 },
];
var spendOutputs = [];
service.buildAddressSummary(address, tip, allOutputs, spendOutputs).should.deep.equal({
address: address.toString(),
transactions: ['A'],
confirmed: { balance: 20, sent: 0, received: 20 },
unconfirmed: { balance: 20, sent: 0, received: 20 }
});
});
it('confirmed spent transactions change the balance', function() {
var allOutputs = [
{ satoshis: 10, txId: 'A', outputIndex: 0, heightConfirmed: 1 },
];
var spendOutputs = [
{ spendInput: { prevTxId: 'A', outputIndex: 0 }, spentTx: 'D', heightSpent: 2 }
];
service.buildAddressSummary(address, tip, allOutputs, spendOutputs).should.deep.equal({
address: address.toString(),
transactions: ['A', 'D'],
confirmed: { balance: 0, sent: 10, received: 10 },
unconfirmed: { balance: 0, sent: 10, received: 10 }
});
});
});
});

View File

@ -42,7 +42,7 @@ describe('BlockService', function() {
size: 215,
height: 2,
version: 1,
merkleRoot: '9b0fc92260312ce44e74ef369f5c66bbb85848f2eddd5a7a1cde251e54ccfdd5',
merkleroot: '9b0fc92260312ce44e74ef369f5c66bbb85848f2eddd5a7a1cde251e54ccfdd5',
tx: [ '9b0fc92260312ce44e74ef369f5c66bbb85848f2eddd5a7a1cde251e54ccfdd5' ],
time: 1231469744,
nonce: 1639830024,

View File

@ -71,7 +71,12 @@ describe('TransactionService', function() {
it('confirms correctly the first transaction on genesis block', function(callback) {
var ops = [];
service._confirmTransaction(ops, genesisBlock, genesisTx).then(function() {
ops.should.deep.equal([
ops.map(function(k) {
if (bitcore.util.js.isValidJSON(k.value)) {
k.value = JSON.parse(k.value);
}
return k;
}).should.deep.equal([
{ type: 'put',
key: 'btx-4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b',
value: '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f' },
@ -84,7 +89,8 @@ describe('TransactionService', function() {
key: 'txa-1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa-4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b-0',
value:
{ satoshis: 5000000000,
script: '65 0x04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f OP_CHECKSIG' } }
script: '65 0x04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f OP_CHECKSIG',
heightConfirmed: 0} }
]);
callback();
});
@ -102,7 +108,12 @@ describe('TransactionService', function() {
}
});
service._confirmTransaction(ops, block170, block170.transactions[1]).then(function() {
ops.should.deep.equal([
ops.map(function(k) {
if (bitcore.util.js.isValidJSON(k.value)) {
k.value = JSON.parse(k.value);
}
return k;
}).should.deep.equal([
{ type: 'put',
key: 'btx-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16',
value: '00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee' },
@ -115,7 +126,8 @@ describe('TransactionService', function() {
key: 'txa-1Q2TWHE3GMdB6BZKafqwxXtWAWgFt5Jvm3-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16-0',
value:
{ satoshis: 1000000000,
script: '65 0x04ae1a62fe09c5f51b13905f07f06b99a2f7159b2225f374cd378d71302fa28414e7aab37397f554a7df5f142c21c1b7303b8a0626f1baded5c72a704f7e6cd84c OP_CHECKSIG' } },
script: '65 0x04ae1a62fe09c5f51b13905f07f06b99a2f7159b2225f374cd378d71302fa28414e7aab37397f554a7df5f142c21c1b7303b8a0626f1baded5c72a704f7e6cd84c OP_CHECKSIG',
heightConfirmed: 170 } },
{ type: 'put',
key: 'txo-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16-1',
value:
@ -125,7 +137,8 @@ describe('TransactionService', function() {
key: 'txa-12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16-1',
value:
{ satoshis: 4000000000,
script: '65 0x0411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3 OP_CHECKSIG' } },
script: '65 0x0411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3 OP_CHECKSIG',
heightConfirmed: 170 } },
{ type: 'put',
key: 'txo-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16-0',
value:
@ -133,7 +146,6 @@ describe('TransactionService', function() {
outputIndex: 0,
sequenceNumber: 4294967295,
script: '71 0x304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901',
output: undefined,
heightConfirmed: 170 } },
{ type: 'put',
key: 'txas-12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16-0',
@ -144,8 +156,7 @@ describe('TransactionService', function() {
spendInput: { prevTxId: '0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9',
outputIndex: 0,
sequenceNumber: 4294967295,
script: '71 0x304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901',
output: undefined }}}]);
script: '71 0x304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901' }}}]);
callback();
});
});