translate address for inputs and outputs

This commit is contained in:
Matias Alejo Garcia 2017-11-12 05:05:39 -03:00
parent 41b304c5a5
commit 5a61a2e34a
9 changed files with 550 additions and 31 deletions

View File

@ -8,12 +8,12 @@ var Common = require('./common');
var _ = require('lodash');
var LRU = require('lru-cache');
function AddressController(node) {
function AddressController(node, translateAddresses) {
this.node = node;
this._address = this.node.services.address;
this._block = this.node.services.block;
this.txController = new TxController(node);
this.common = new Common({log: this.node.log});
this.txController = new TxController(node, translateAddresses);
this.common = new Common({log: this.node.log, translateAddresses: translateAddresses});
this._block = this.node.services.block;
this._utxoCache = new LRU({
max: 250,
@ -73,6 +73,7 @@ AddressController.prototype.addressSummarySubQuery = function(req, res, param) {
};
AddressController.prototype.getAddressSummary = function(address, options, callback) {
var self = this;
this._address.getAddressSummary(address, options, function(err, summary) {
if(err) {
@ -80,7 +81,7 @@ AddressController.prototype.getAddressSummary = function(address, options, callb
}
var transformed = {
addrStr: address,
address: self.common.translateOutputAddress(address),
balance: Unit.fromSatoshis(summary.balance).toBTC(),
balanceSat: summary.balance,
totalReceived: Unit.fromSatoshis(summary.totalReceived).toBTC(),
@ -99,6 +100,7 @@ AddressController.prototype.getAddressSummary = function(address, options, callb
};
AddressController.prototype.checkAddrs = function(req, res, next) {
var self = this;
function makeArray(addrs) {
if (_.isString(addrs)) {
@ -123,29 +125,18 @@ AddressController.prototype.checkAddrs = function(req, res, next) {
}, res);
}
var inValid = this.check(req.addrs);
if (inValid) {
try {
req.addrs = self.common.translateInputAddresses(req.addrs);
req.addr = req.addrs[0];
} catch(e) {
console.log('[addresses.js.130]', e); //TODO
return this.common.handleErrors({
message: 'Invalid address: ' + inValid.message,
message: 'Invalid address: ' + e,
code: 1
}, res);
}
};
next();
};
AddressController.prototype.check = function(addresses) {
for(var i = 0; i < addresses.length; i++) {
try {
new bitcore.Address(addresses[i]);
} catch(e) {
return addresses[i];
}
}
};
AddressController.prototype.utxo = function(req, res) {
@ -170,9 +161,11 @@ AddressController.prototype.utxo = function(req, res) {
});
};
AddressController.prototype.transformUtxo = function(utxoArg) {
var utxo = {
address: utxoArg.address,
address: this.common.translateOutputAddress(utxoArg.address),
txid: utxoArg.txid,
vout: utxoArg.vout,
scriptPubKey: utxoArg.scriptPubKey,

56
lib/addresstranslator.js Normal file
View File

@ -0,0 +1,56 @@
var Bitcore_ = {
btc: require('bitcore-lib'),
bch: require('bitcore-lib-cash')
};
var _ = require('lodash');
function AddressTranslator() {
};
AddressTranslator.getAddressCoin = function(address) {
try {
new Bitcore_['btc'].Address(address);
return 'btc';
} catch (e) {
try {
new Bitcore_['bch'].Address(address);
return 'bch';
} catch (e) {
return;
}
}
};
AddressTranslator.translate = function(addresses, coin, origCoin) {
var wasArray = true;
if (!_.isArray(addresses)) {
wasArray = false;
addresses = [addresses];
}
origCoin = origCoin || AddressTranslator.getAddressCoin(addresses[0]);
var ret = _.map(addresses, function(x) {
var orig = new Bitcore_[origCoin].Address(x).toObject();
return Bitcore_[coin].Address.fromObject(orig).toString();
});
if (wasArray)
return ret;
else
return ret[0];
};
AddressTranslator.translateInput = function(addresses) {
return this.translate(addresses, 'btc', 'bch');
}
AddressTranslator.translateOutput = function(addresses) {
return this.translate(addresses, 'bch', 'btc');
}
module.exports = AddressTranslator;

View File

@ -1,7 +1,12 @@
'use strict';
var _ = require('lodash');
var AddressTranslator = require('./addresstranslator');
var bitcore = require('bitcore-lib');
function Common(options) {
this.log = options.log;
this.translateAddresses = options.translateAddresses;
}
Common.prototype.notReady = function (err, res, p) {
@ -21,4 +26,43 @@ Common.prototype.handleErrors = function (err, res) {
}
};
Common.prototype.translateInputAddresses= function(addresses) {
var self = this;
if (!addresses) return;
if (!_.isArray(addresses))
addresses = [ addresses ];
function check(addresses) {
if (!addresses) return;
for(var i = 0; i < addresses.length; i++) {
try {
new bitcore.Address(addresses[i]);
} catch(e) {
throw addresses[i];
}
}
}
if (this.translateAddresses) {
addresses = AddressTranslator.translateInput(addresses);
} else
check(addresses);
return addresses;
};
Common.prototype.translateOutputAddress= function(address) {
if (!this.translateAddresses) return address;
return AddressTranslator.translateOutput(address);
};
module.exports = Common;

View File

@ -27,6 +27,7 @@ var EventEmitter = require('events').EventEmitter;
* @param {Number} options.cacheShortSeconds - The time to cache short lived cache responses.
* @param {Number} options.cacheLongSeconds - The time to cache long lived cache responses.
* @param {String} options.routePrefix - The URL route prefix
* @param {String} options.translateAddresses - Translate request and output address to Copay's BCH address version (see https://support.bitpay.com/hc/en-us/articles/115004671663-BitPay-s-Adopted-Conventions-for-Bitcoin-Cash-Addresses-URIs-and-Payment-Requests)
*/
var InsightAPI = function(options) {
BaseService.call(this, options);
@ -47,6 +48,7 @@ var InsightAPI = function(options) {
this.rateLimiterOptions = options.rateLimiterOptions;
this.disableRateLimiter = options.disableRateLimiter;
this.translateAddresses = options.translateAddresses;
this.blockSummaryCacheSize = options.blockSummaryCacheSize || BlockController.DEFAULT_BLOCKSUMMARY_CACHE_SIZE;
this.blockCacheSize = options.blockCacheSize || BlockController.DEFAULT_BLOCK_CACHE_SIZE;
@ -201,7 +203,7 @@ InsightAPI.prototype.setupRoutes = function(app) {
app.param('height', blocks.blockIndex.bind(blocks));
// Transaction routes
var transactions = new TxController(this.node);
var transactions = new TxController(this.node, this.translateAddresses);
app.get('/tx/:txid', this.cacheShort(), transactions.show.bind(transactions));
app.param('txid', transactions.transaction.bind(transactions));
app.get('/txs', this.cacheShort(), transactions.list.bind(transactions));
@ -212,7 +214,7 @@ InsightAPI.prototype.setupRoutes = function(app) {
app.param('txid', transactions.rawTransaction.bind(transactions));
// Address routes
var addresses = new AddressController(this.node);
var addresses = new AddressController(this.node, this.translateAddresses);
app.get('/addr/:addr', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.show.bind(addresses));
app.get('/addr/:addr/utxo', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.utxo.bind(addresses));
app.get('/addrs/:addrs/utxo', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.multiutxo.bind(addresses));

View File

@ -8,9 +8,9 @@ var async = require('async');
var MAXINT = 0xffffffff; // Math.pow(2, 32) - 1;
function TxController(node) {
function TxController(node, translateAddresses) {
this.node = node;
this.common = new Common({log: this.node.log});
this.common = new Common({log: this.node.log, translateAddresses: translateAddresses});
this._block = this.node.services.block;
this._transaction = this.node.services.transaction;
this._address = this.node.services.address;
@ -134,7 +134,8 @@ TxController.prototype.transformInput = function(options, input, index) {
var address = input.getAddress();
if (address) {
address.network = this._network;
transformed.addr = address.toString();
transformed.addr = this.common.translateOutputAddress(address.toString());
} else {
transformed.addr = null;
}
@ -170,13 +171,16 @@ TxController.prototype.transformOutput = function(options, output, index) {
var address = output.getAddress();
if (address) {
address.network = this._network;
transformed.scriptPubKey.addresses = [address.toString()];
transformed.scriptPubKey.addresses = [this.common.translateOutputAddress(address.toString())];
transformed.scriptPubKey.type = address.getType();
}
return transformed;
};
TxController.prototype.transformInvTransaction = function(transaction) {
var self = this;
var valueOut = 0;
var vout = [];
for (var i = 0; i < transaction.outputs.length; i++) {
@ -190,7 +194,7 @@ TxController.prototype.transformInvTransaction = function(transaction) {
}
address.network = this._network;
address = address.toString();
address = self.common.translateOutputAddress(address.toString());
var obj = {};
obj[address] = output.value;
@ -244,7 +248,7 @@ TxController.prototype.list = function(req, res) {
var self = this;
var blockHash = req.query.block;
var address = req.query.address;
var address = this.common.translateInputAddresses(req.query.address);
var page = parseInt(req.query.pageNum) || 0;
var pageLength = 10;
var pagesTotal = 1;

60
package-lock.json generated
View File

@ -80,6 +80,14 @@
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
},
"base-x": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.2.tgz",
"integrity": "sha1-v4c4YbdRQnm3lp80CSnquHwR0TA=",
"requires": {
"safe-buffer": "5.1.1"
}
},
"base64-arraybuffer": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz",
@ -184,6 +192,58 @@
}
}
},
"bitcore-lib-cash": {
"version": "0.15.1",
"resolved": "https://registry.npmjs.org/bitcore-lib-cash/-/bitcore-lib-cash-0.15.1.tgz",
"integrity": "sha512-2fftReEqbkohCwiblBGteEX5S7lc6oTGM1QyDr6rz88QA7xLqhtVkT9hWrwirZfw6H4IfApS2gAAtHGExCqMsw==",
"requires": {
"bn.js": "4.11.8",
"bs58": "4.0.1",
"buffer-compare": "1.1.1",
"elliptic": "6.4.0",
"inherits": "2.0.1",
"lodash": "4.17.4"
},
"dependencies": {
"bn.js": {
"version": "4.11.8",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
},
"bs58": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz",
"integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=",
"requires": {
"base-x": "3.0.2"
}
},
"buffer-compare": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-compare/-/buffer-compare-1.1.1.tgz",
"integrity": "sha1-W+e+hTr4kZjR9N3AkNHWakiu9ZY="
},
"elliptic": {
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz",
"integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=",
"requires": {
"bn.js": "4.11.8",
"brorand": "1.1.0",
"hash.js": "1.1.3",
"hmac-drbg": "1.0.1",
"inherits": "2.0.1",
"minimalistic-assert": "1.0.0",
"minimalistic-crypto-utils": "1.0.1"
}
},
"lodash": {
"version": "4.17.4",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
"integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
}
}
},
"bitcore-message": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bitcore-message/-/bitcore-message-1.0.2.tgz",

View File

@ -32,6 +32,7 @@
"async": "*",
"bcoin": "bcoin-org/bcoin#886008a1822ce1da7fa8395ee7db4bcc1750a28a",
"bitcore-lib": "5.0.0-beta.1",
"bitcore-lib-cash": "0.15.1",
"bitcore-message": "^1.0.1",
"body-parser": "^1.13.3",
"compression": "^1.6.1",

306
test/addressesCash.js Normal file
View File

@ -0,0 +1,306 @@
'use strict';
var should = require('should');
var sinon = require('sinon');
var AddressController = require('../lib/addresses');
var _ = require('lodash');
var bitcore = require('bitcore-lib');
var bcoin = require('bcoin');
var rawHex = "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d010bffffffff0100f2052a010000004341047211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073dee6c89064984f03385237d92167c13e236446b417ab79a0fcae412ae3316b77ac00000000";
var bcoinTx = bcoin.tx.fromRaw(rawHex, 'hex');
bcoinTx.__blockhash = '0000000000000041ddc94ecf4f86a456a83b2e320c36c6f0c13ff92c7e75f013';
bcoinTx.blockhash = '0000000000000041ddc94ecf4f86a456a83b2e320c36c6f0c13ff92c7e75f013';
bcoinTx.__height = 534181;
bcoinTx.__timestamp = 1441116143;
bcoinTx.outputSatoshis = 53829829;
var txinfos2 = {
totalCount: 1,
items: [ bcoinTx ]
};
var utxos = [
{
'address': '1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA',
'txid': '63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73',
'vout': 1,
'timestamp': 1441116143,
'satoshis': 53320000,
'scriptPubKey': '76a914d2ec20bb8e5f25a52f730384b803d95683250e0b88ac',
'height': 534181,
'confirmations': 50
},
{
'address': '3EDL9HSincwLGfYbWPQ7LXtc4VqdwGoraS',
'txid': '63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73',
'vout': 2,
'timestamp': 1441116143,
'satoshis': 289829,
'scriptPubKey': '76a914583df9fa56ad961051e00ca93e68dfaf1eab9ec588ac',
'height': 534181,
'confirmations': 50
}
];
describe('Addresses / Bitcoin Cash', function() {
var summary = {
addrStr: 'CcJ4qUfyQ8x5NwhAeCQkrBSWVeXxXghcNz',
balance: 0,
totalReceivedSat: 2782729129,
totalSentSat: 2782729129,
unconfirmedBalance: 0,
appearances: 2,
unconfirmedAppearances: 0,
txids: [
'bb0ec3b96209fac9529570ea6f83a86af2cceedde4aaf2bfcc4796680d23f1c7',
'01f700df84c466f2a389440e5eeacdc47d04f380c39e5d19dce2ce91a11ecba3'
]
};
describe('/addr/:addr', function() {
var node = {
services: {
address: {
getAddressSummary: sinon.stub().callsArgWith(2, null, summary)
}
}
};
var addresses = new AddressController(node, true);
var req = {
addr: '',
query: {}
};
it('checkAddrs', function(done) {
var insight = 0;
var req = {
query: {
noTxList: 1
},
params: {
addr: 'CcJ4qUfyQ8x5NwhAeCQkrBSWVeXxXghcNz'
},
};
var send = sinon.stub();
var status = sinon.stub().returns({send: send});
var res = {
status: status
};
addresses.checkAddrs(req, res, function(req2) {
req.addr.should.equal('1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA');
req.addrs[0].should.equal('1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA');
done();
});
});
});
describe('/addr/:addr/utxo', function() {
it('should have correct data', function(done) {
var insight = [
{
'address': 'CcJ4qUfyQ8x5NwhAeCQkrBSWVeXxXghcNz',
'txid': '63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73',
'vout': 1,
'ts': 1441116143,
'scriptPubKey': '76a914d2ec20bb8e5f25a52f730384b803d95683250e0b88ac',
'amount': 0.5332,
'confirmations': 50,
'height': 534181,
'satoshis': 53320000,
'confirmationsFromCache': true
}
];
var todos = [
{
confirmationsFromCache: true
}
];
var node = {
services: {
block: {
getTip: sinon.stub().returns({ height: 534230 })
},
address: {
getAddressUnspentOutputs: sinon.stub().callsArgWith(2, null, utxos.slice(0, 1))
}
},
};
var addresses = new AddressController(node, true);
var req = {
addr: 'CcJ4qUfyQ8x5NwhAeCQkrBSWVeXxXghcNz'
};
var res = {
jsonp: function(data) {
var merged = _.merge(data, todos);
should(merged).eql(insight);
done();
}
};
addresses.utxo(req, res);
});
});
describe('/addrs/:addrs/utxo', function() {
it('should have the correct data', function(done) {
var insight = [
{
'address': 'CcJ4qUfyQ8x5NwhAeCQkrBSWVeXxXghcNz',
'txid': '63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73',
'vout': 1,
'ts': 1441116143,
'scriptPubKey': '76a914d2ec20bb8e5f25a52f730384b803d95683250e0b88ac',
'amount': 0.5332,
'height': 534181,
'satoshis': 53320000,
'confirmations': 50
},
{
'address': 'HK3Sc5sodw9ztqRdN54GJvR969rejftcS9',
'txid': '63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73',
'vout': 2,
'ts': 1441116143,
'scriptPubKey': '76a914583df9fa56ad961051e00ca93e68dfaf1eab9ec588ac',
'amount': 0.00289829,
'height': 534181,
'satoshis': 289829,
'confirmations': 50
}
];
var utxoStub = sinon.stub();
utxoStub.onCall(0).callsArgWith(2, null, [utxos[0]]);
utxoStub.onCall(1).callsArgWith(2, null, [utxos[1]]);
var node = {
services: {
address: {
getAddressUnspentOutputs: utxoStub
},
block: {
getTip: sinon.stub().returns({ height: 534230 })
}
},
};
var addresses = new AddressController(node, true);
var req = {
addrs: 'mzkD4nmQ8ixqxySdBgsXTpgvAMK5iRZpNK,moZY18rGNmh4YCPeugtGW46AkkWMQttBUD'
};
var finalData = '';
var res = {
write: function(data) {
finalData += data;
},
end: function() {
var finalObject = JSON.parse(finalData);
finalObject.should.eql(insight);
done();
}
};
addresses.multiutxo(req, res);
});
});
describe('/addrs/:addrs/txs', function() {
it('should have correct data', function(done) {
var insight = {
'totalItems': 1,
'from': 0,
'to': 1,
'items': [
{
'txid': '9b0fc92260312ce44e74ef369f5c66bbb85848f2eddd5a7a1cde251e54ccfdd5',
'version': 1,
'isCoinBase': true,
'locktime': 0,
'vin': [
{
'coinbase': '04ffff001d010b',
'sequence': 4294967295,
'n': 0
}
],
'vout': [
{
'value': '50.00000000',
'n': 0,
'scriptPubKey': {
'asm': '047211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073dee6c89064984f03385237d92167c13e236446b417ab79a0fcae412ae3316b77 OP_CHECKSIG',
'hex': '41047211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073dee6c89064984f03385237d92167c13e236446b417ab79a0fcae412ae3316b77ac',
'type': 'pubkeyhash',
'addresses': [
'CYognBa8KGDnHMcyM7siKgxRkCkUqLb4YM'
]
},
spentHeight: null,
spentIndex: null,
spentTxId: null
}
],
'blockhash': '0000000000000041ddc94ecf4f86a456a83b2e320c36c6f0c13ff92c7e75f013',
'blockheight': 534181,
'confirmations': 52,
'time': 1441116143,
'blocktime': 1441116143,
'valueOut': 0.53829829,
'size': 134
}
]
};
var node = {
services: {
address: {
getAddressHistory: sinon.stub().callsArgWith(2, null, txinfos2),
},
block: {
getTip: sinon.stub().returns({ height: 534232 })
}
}
};
var addresses = new AddressController(node, true);
var req = {
addrs: 'CcJ4qUfyQ8x5NwhAeCQkrBSWVeXxXghcNz,HK3Sc5sodw9ztqRdN54GJvR969rejftcS9',
query: {},
body: {}
};
var res = {
jsonp: function(data) {
should(data).eql(insight);
done();
}
};
addresses.multitxs(req, res);
});
});
});

View File

@ -0,0 +1,53 @@
var _ = require('lodash');
var chai = require('chai');
var sinon = require('sinon');
var assert = require('assert');
var should = chai.should;
var AddressTranslator = require('../lib/addresstranslator');
describe('#AddressTranslator', function() {
it('should translate address from btc to bch', function() {
var res = AddressTranslator.translate('1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA', 'bch');
assert( res == 'CcJ4qUfyQ8x5NwhAeCQkrBSWVeXxXghcNz');
});
it('should translate address from bch to btc', function() {
var res = AddressTranslator.translateInput('HBf8isgS8EXG1r3X6GP89FmooUmiJ42wHS');
assert(res=='36q2G5FMGvJbPgAVEaiyAsFGmpkhPKwk2r');
});
it('should keep the address if there is nothing to do (bch)', function() {
var res = AddressTranslator.translate('CcJ4qUfyQ8x5NwhAeCQkrBSWVeXxXghcNz', 'bch');
assert(res=='CcJ4qUfyQ8x5NwhAeCQkrBSWVeXxXghcNz');
});
it('should keep the address if there is nothing to do (btc)', function() {
var res = AddressTranslator.translate('1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA', 'btc');
assert(res=='1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA');
});
it('should support 3 params NOK', function() {
var a;
try {
var res = AddressTranslator.translate('1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA', 'btc', 'bch');
} catch (e) {
a=e.toString();
assert(a.match(/Address has mismatched network type/));
};
});
it('should support 3 params OK', function() {
var res = AddressTranslator.translate('HBf8isgS8EXG1r3X6GP89FmooUmiJ42wHS', 'btc', 'bch');
assert(res=='36q2G5FMGvJbPgAVEaiyAsFGmpkhPKwk2r');
});
it('should work with arrays also', function() {
var res = AddressTranslator.translateOutput(['1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA', '37YHiaQnMjy73GS1UpiE8p2Ju6MyrrDw3J', '1DuPdCpGzVX73kBYaAbu5XDNDgE2Lza5Ed']);
assert(res[0] == 'CcJ4qUfyQ8x5NwhAeCQkrBSWVeXxXghcNz');
assert(res[1] == 'HCNQBNqsD4BmfSK3LWNP7CYqvkNznSrXS3');
assert(res[2] == 'CVNHCFALsYVdwt5yFuvpf2qPqoSSGtvY7t');
});
});