openassets-js
Version:
JavaScript implementation of the Open Assets Protocol
421 lines (362 loc) • 19.2 kB
JavaScript
;
process.env.NODE_ENV = process.env.NODE_ENV || 'test';
var assert = require('assert'),
async = require('async'),
chai = require('chai'),
expect = chai.expect,
should = chai.should(),
sinon = require('sinon'),
ColoringEngine = require('../lib/protocol/ColoringEngine'),
OutputType = require('../lib/protocol/OutputType'),
TransactionOutput = require('../lib/protocol/TransactionOutput'),
bitcore = require('bitcore'),
RpcClient = require('bitcore/lib/RpcClient'),
EncodedData = require('bitcore/util/EncodedData');
describe("TransactionOutput", function () {
describe('::constructor', function () {
});
describe('::_computeAssetIds', function () {
var config, getTransactionProvider, ce,
inputs, outputs, markerOutputIndex, assetQuantities, result;
beforeEach(function () {
// RPC connection information
config = {
host: process.env.JSONRPC_HOST,
port: process.env.JSONRPC_PORT,
user: process.env.JSONRPC_USER,
pass: process.env.JSONRPC_PASS,
protocol: process.env.JSONRPC_PROTOCOL
};
// A wrapper to generate transaction providers given a config
getTransactionProvider = function getTransactionProvider(config) {
return function transactionProvider(hash, cb) {
var rpcc = new RpcClient(config);
rpcc.getRawTransaction(hash,cb);
};
};
// Create an instance of the Coloring Engine
ce = new ColoringEngine(getTransactionProvider(config));
})
it('should compute the asset id of an Issuance output', function (done) {
// This is a simulation of the following transaction:
// https://www.coinprism.info/tx/77a6bbc65aa0326015835a3813778df4a037c15fb655e8678f234d8e2fc7439c
// https://insight.bitpay.com/tx/77a6bbc65aa0326015835a3813778df4a037c15fb655e8678f234d8e2fc7439c
inputs = [
new TransactionOutput(100000, new Buffer('76a914d717483b5554670550f8e79a3b958d294ecf806088ac','hex'))
];
markerOutputIndex=1;
outputs = [
{ v: new Buffer('5802000000000000','hex'),
s: new Buffer('76a914d717483b5554670550f8e79a3b958d294ecf806088ac','hex')},
{ v: new Buffer('0000000000000000','hex'),
s: new Buffer('6a224f41010001011b753d68747470733a2f2f6370722e736d2f63463557584459643642','hex')},
{ v: new Buffer('385d010000000000','hex'),
s: new Buffer('76a914d717483b5554670550f8e79a3b958d294ecf806088ac','hex')}
];
assetQuantities = [1];
result = ce._computeAssetIds(inputs, markerOutputIndex, outputs, assetQuantities);
result.length.should.equal(3);
// Issuance output
result[0].value.should.equal(600);
result[0].outputType.should.equal(OutputType.ISSUANCE);
result[0].assetId.toString('hex').should.equal('1d27fd8fac0cda221b3fccc6ecc1fc46cd9178d0');
result[0].toString().should.equal('TransactionOutput(value=600, script=0x76a914d717483b5554670550f8e79a3b958d294ecf806088ac, assetId=0x1d27fd8fac0cda221b3fccc6ecc1fc46cd9178d0, assetQuantity=1, outputType=ISSUANCE)');
// Marker output
result[1].value.should.equal(0);
result[1].outputType.should.equal(OutputType.MARKER_OUTPUT);
expect(result[1].assetId).to.be.null;
result[1].toString().should.equal('TransactionOutput(value=0, script=0x6a224f41010001011b753d68747470733a2f2f6370722e736d2f63463557584459643642, assetId=null, assetQuantity=null, outputType=MARKER_OUTPUT)');
// Transfer output (technically "uncolored" since this there is no asset id - this is just a "change" output)
expect(result[2].assetId).to.be.null;
expect(result[2].assetQuantity).to.be.null;
result[2].value.should.equal(89400);
result[2].outputType.should.equal(OutputType.TRANSFER);
done();
});
it('should compute the asset id for a Transfer from an Issuance', function (done) {
// This is a simulation of the following transaction:
// https://www.coinprism.info/tx/b26bbbf5cf5a783241397236227640402ecdd10955f8aecf2ad19b3b744bde42
// https://insight.bitpay.com/tx/b26bbbf5cf5a783241397236227640402ecdd10955f8aecf2ad19b3b744bde42
inputs = [
new TransactionOutput(600, new Buffer('76a914d717483b5554670550f8e79a3b958d294ecf806088ac','hex'), new Buffer('1d27fd8fac0cda221b3fccc6ecc1fc46cd9178d0','hex'), 1, OutputType.ISSUANCE),
new TransactionOutput(0, new Buffer('6a224f41010001011b753d68747470733a2f2f6370722e736d2f63463557584459643642', null, null, OutputType.MARKER_OUTPUT))
];
markerOutputIndex=0;
outputs = [
{ v: new Buffer('0000000000000000','hex'),
s: new Buffer('6a074f410100010100','hex')},
{ v: new Buffer('5802000000000000','hex'),
s: new Buffer('76a91475c37d8aaeb2cd9859a7b212d21e422903cf00a288ac','hex')},
{ v: new Buffer('2836010000000000','hex'),
s: new Buffer('76a914d717483b5554670550f8e79a3b958d294ecf806088ac','hex')}
];
assetQuantities = [1];
result = ce._computeAssetIds(inputs, markerOutputIndex, outputs, assetQuantities);
result.length.should.equal(3);
// Marker output
result[0].value.should.equal(0);
result[0].outputType.should.equal(OutputType.MARKER_OUTPUT);
expect(result[0].assetId).to.be.null;
result[0].toString().should.equal('TransactionOutput(value=0, script=0x6a074f410100010100, assetId=null, assetQuantity=null, outputType=MARKER_OUTPUT)');
// Transfer output
result[1].value.should.equal(600);
result[1].outputType.should.equal(OutputType.TRANSFER);
result[1].assetId.toString('hex').should.equal('1d27fd8fac0cda221b3fccc6ecc1fc46cd9178d0');
result[1].toString().should.equal('TransactionOutput(value=600, script=0x76a91475c37d8aaeb2cd9859a7b212d21e422903cf00a288ac, assetId=0x1d27fd8fac0cda221b3fccc6ecc1fc46cd9178d0, assetQuantity=1, outputType=TRANSFER)');
// Transfer output (technically "uncolored" since this there is no asset id - this is just a "change" output)
expect(result[2].assetId).to.be.null;
expect(result[2].assetQuantity).to.be.null;
result[2].value.should.equal(79400);
result[2].outputType.should.equal(OutputType.TRANSFER);
done();
});
it('should compute the asset id for a Transfer from a Transfer', function (done) {
// This is a simulation of the following transaction:
// https://www.coinprism.info/tx/56a4bde85b9f2de5be0f17ad0fa666efebbf6cc7961b2720bdf0750282f8d18c
// https://insight.bitpay.com/tx/56a4bde85b9f2de5be0f17ad0fa666efebbf6cc7961b2720bdf0750282f8d18c
inputs = [
new TransactionOutput(600, new Buffer('76a91475c37d8aaeb2cd9859a7b212d21e422903cf00a288ac','hex'), new Buffer('1d27fd8fac0cda221b3fccc6ecc1fc46cd9178d0','hex'), 1, OutputType.TRANSFER),
new TransactionOutput(0, new Buffer('6a224f41010001011b753d68747470733a2f2f6370722e736d2f63463557584459643642', null, null, OutputType.MARKER_OUTPUT))
];
markerOutputIndex=0;
outputs = [
{ v: new Buffer('0000000000000000','hex'),
s: new Buffer('6a074f410100010100','hex')},
{ v: new Buffer('5802000000000000','hex'),
s: new Buffer('76a914d717483b5554670550f8e79a3b958d294ecf806088ac','hex')},
{ v: new Buffer('905f010000000000','hex'),
s: new Buffer('76a91475c37d8aaeb2cd9859a7b212d21e422903cf00a288ac','hex')}
];
assetQuantities = [1];
result = ce._computeAssetIds(inputs, markerOutputIndex, outputs, assetQuantities);
result.length.should.equal(3);
// Marker output
result[0].value.should.equal(0);
result[0].outputType.should.equal(OutputType.MARKER_OUTPUT);
expect(result[0].assetId).to.be.null;
result[0].toString().should.equal('TransactionOutput(value=0, script=0x6a074f410100010100, assetId=null, assetQuantity=null, outputType=MARKER_OUTPUT)');
// Transfer output
result[1].value.should.equal(600);
result[1].outputType.should.equal(OutputType.TRANSFER);
result[1].assetId.toString('hex').should.equal('1d27fd8fac0cda221b3fccc6ecc1fc46cd9178d0');
result[1].toString().should.equal('TransactionOutput(value=600, script=0x76a914d717483b5554670550f8e79a3b958d294ecf806088ac, assetId=0x1d27fd8fac0cda221b3fccc6ecc1fc46cd9178d0, assetQuantity=1, outputType=TRANSFER)');
// Transfer output (technically "uncolored" since this there is no asset id - this is just a "change" output)
expect(result[2].assetId).to.be.null;
expect(result[2].assetQuantity).to.be.null;
result[2].value.should.equal(90000);
result[2].outputType.should.equal(OutputType.TRANSFER);
done();
});
});
describe('::getOutput', function () {
var txFunding, txIssue1, txIssue2, txXfer1, txXfer2, txXfer3,
config, getTransactionProvider, ce,
seriesCallback;
beforeEach( function () {
// Set up a few transaction hashes to use with testing
// see https://www.coinprism.info/tx/<transactionhash> for information on each:
// Transfer 0.001 BTC to address: 1LcJAv8c81q9BRv45znsVSdsuMRtKtpzKL
txFunding = '405b36e856c6f89952116948268ffcd4deffb845c232514cf81a324f343eddf5';
// Issue 1 'AJS39eYsPGYo3S8L73xWGv8DwPHT4LYp8B' asset to address: akWaBR5wwbViq3g5R8cu49JYTExc4GrNpeT
txIssue1 = '77a6bbc65aa0326015835a3813778df4a037c15fb655e8678f234d8e2fc7439c';
// Transfer 1 'AJS39eYsPGYo3S8L73xWGv8DwPHT4LYp8B' asset to address: akMhZVXQsR7mUkud8LGDoQbnP3um6iFF1JC
txXfer1 = 'b26bbbf5cf5a783241397236227640402ecdd10955f8aecf2ad19b3b744bde42';
// (return) Transfer the 1 'AJS39eYsPGYo3S8L73xWGv8DwPHT4LYp8B' asset back to address: akWaBR5wwbViq3g5R8cu49JYTExc4GrNpeT
txXfer2 = '56a4bde85b9f2de5be0f17ad0fa666efebbf6cc7961b2720bdf0750282f8d18c';
// Issue 200 'AJS39eYsPGYo3S8L73xWGv8DwPHT4LYp8B' assets to address: akWaBR5wwbViq3g5R8cu49JYTExc4GrNpeT
txIssue2 = 'dcccfdf72171964bfa895def45eeca746b126159473162a74d2c99c88bba469d';
// Transfer 2 'AJS39eYsPGYo3S8L73xWGv8DwPHT4LYp8B' assets to address akMhZVXQsR7mUkud8LGDoQbnP3um6iFF1JC
txXfer3 = '5bcc5beaf1ded56e22757b05329fb00c8e37f53593ec56e7303e0a8c99ecd169';
// RPC connection information
config = {
host: process.env.JSONRPC_HOST,
port: process.env.JSONRPC_PORT,
user: process.env.JSONRPC_USER,
pass: process.env.JSONRPC_PASS,
protocol: process.env.JSONRPC_PROTOCOL
};
// A wrapper to generate transaction providers given a config
getTransactionProvider = function getTransactionProvider(config) {
return function transactionProvider(hash, cb) {
var rpcc = new RpcClient(config);
rpcc.getRawTransaction(hash,cb);
};
};
// Create an instance of the Coloring Engine
ce = new ColoringEngine(getTransactionProvider(config));
});
it('should handle a non-Open Assets transaction given a valid hash', function (done) {
this.timeout(5000);
async.series([
function (cb) {
ce.getOutput(txFunding, 0, function (err, data) {
expect(err).to.not.exist;
expect(data.toString()).to.equal(
'TransactionOutput(value=100000, script=0x76a914d717483b5554670550f8e79a3b958d294ecf806088ac, assetId=null, assetQuantity=null, outputType=UNCOLORED)');
cb();
});
},
function (cb) {
ce.getOutput(txFunding, 1, function (err, data) {
expect(err).to.not.exist;
expect(data.toString()).to.equal(
'TransactionOutput(value=390000, script=0x76a9141937a416988ea705aa9c9112e67a35543c6854fb88ac, assetId=null, assetQuantity=null, outputType=UNCOLORED)');
cb();
});
}
],function () {done();});
});
it('should detect an Open Assets issuance transaction output', function (done) {
async.series([
// Open Assets Issuance output
function (cb) {
ce.getOutput(txIssue1, 0, function (err, data) {
expect(err).to.not.exist;
expect(data.toString()).to.equal(
'TransactionOutput(value=600, script=0x76a914d717483b5554670550f8e79a3b958d294ecf806088ac, assetId=0x1d27fd8fac0cda221b3fccc6ecc1fc46cd9178d0, assetQuantity=1, outputType=ISSUANCE)');
cb();
});
},
// Open Assets Marker output
function (cb) {
ce.getOutput(txIssue1, 1, function (err, data) {
expect(err).to.not.exist;
expect(data.toString()).to.equal(
'TransactionOutput(value=0, script=0x6a224f41010001011b753d68747470733a2f2f6370722e736d2f63463557584459643642, assetId=null, assetQuantity=null, outputType=MARKER_OUTPUT)');
cb();
});
}
],function () {done();});
});
it('should detect an Open Assets transfer transaction output', function (done) {
this.timeout(5000);
async.series([
// Open Assets Marker output
function (cb) {
ce.getOutput(txXfer1, 0, function (err, data) {
expect(err).to.not.exist;
expect(data.toString()).to.equal(
'TransactionOutput(value=0, script=0x6a074f410100010100, assetId=null, assetQuantity=null, outputType=MARKER_OUTPUT)');
cb();
});
},
// Open Assets Transfer output
function (cb) {
ce.getOutput(txXfer1, 1, function (err, data) {
expect(err).to.not.exist;
expect(data.toString()).to.equal(
'TransactionOutput(value=600, script=0x76a91475c37d8aaeb2cd9859a7b212d21e422903cf00a288ac, assetId=0x1d27fd8fac0cda221b3fccc6ecc1fc46cd9178d0, assetQuantity=1, outputType=TRANSFER)');
cb();
});
}
],function () {done();});
});
it('should detect an Open Assets issuance transaction output', function (done) {
async.series([
// Open Assets Issuance output
function (cb) {
ce.getOutput(txIssue2, 0, function (err, data) {
expect(err).to.not.exist;
expect(data.toString()).to.equal(
'TransactionOutput(value=600, script=0x76a914d717483b5554670550f8e79a3b958d294ecf806088ac, assetId=0x1d27fd8fac0cda221b3fccc6ecc1fc46cd9178d0, assetQuantity=200, outputType=ISSUANCE)');
cb();
});
},
// Open Assets Marker output
function (cb) {
ce.getOutput(txIssue2, 1, function (err, data) {
expect(err).to.not.exist;
expect(data.toString()).to.equal(
'TransactionOutput(value=0, script=0x6a234f41010001c8011b753d68747470733a2f2f6370722e736d2f63463557584459643642, assetId=null, assetQuantity=null, outputType=MARKER_OUTPUT)');
cb();
});
}
],function () {done();});
});
it('should detect an Open Assets transfer transaction output', function (done) {
this.timeout(5000);
async.series([
// Open Assets Marker output
function (cb) {
ce.getOutput(txXfer2, 0, function (err, data) {
expect(err).to.not.exist;
expect(data.toString()).to.equal(
'TransactionOutput(value=0, script=0x6a074f410100010100, assetId=null, assetQuantity=null, outputType=MARKER_OUTPUT)');
cb();
});
},
// Open Assets Transfer output
function (cb) {
ce.getOutput(txXfer2, 1, function (err, data) {
expect(err).to.not.exist;
expect(data.toString()).to.equal(
'TransactionOutput(value=600, script=0x76a914d717483b5554670550f8e79a3b958d294ecf806088ac, assetId=0x1d27fd8fac0cda221b3fccc6ecc1fc46cd9178d0, assetQuantity=1, outputType=TRANSFER)');
cb();
});
}
],function () {done();});
});
it('should detect an Open Assets transfer transaction output with "asset change"', function (done) {
this.timeout(5000);
async.series([
// Open Assets Marker output
function (cb) {
ce.getOutput(txXfer3, 0, function (err, data) {
expect(err).to.not.exist;
expect(data.toString()).to.equal(
'TransactionOutput(value=0, script=0x6a094f4101000202c70100, assetId=null, assetQuantity=null, outputType=MARKER_OUTPUT)');
cb();
});
},
// Open Assets Transfer output (Transfer 1 units from tx 56a...18c and 1 unit from tx dcc...69d to 1BjGFM...)
function (cb) {
ce.getOutput(txXfer3, 1, function (err, data) {
expect(err).to.not.exist;
expect(data.toString()).to.equal(
'TransactionOutput(value=600, script=0x76a91475c37d8aaeb2cd9859a7b212d21e422903cf00a288ac, assetId=0x1d27fd8fac0cda221b3fccc6ecc1fc46cd9178d0, assetQuantity=2, outputType=TRANSFER)');
cb();
});
},
// Open Assets Transfer output (Transfer the remaining 199 units from tx dcc...69d back to the 1LcJAv...)
function (cb) {
ce.getOutput(txXfer3, 2, function (err, data) {
expect(err).to.not.exist;
expect(data.toString()).to.equal(
'TransactionOutput(value=600, script=0x76a914d717483b5554670550f8e79a3b958d294ecf806088ac, assetId=0x1d27fd8fac0cda221b3fccc6ecc1fc46cd9178d0, assetQuantity=199, outputType=TRANSFER)');
cb();
});
},
// Transfer output (technically "uncolored" since there is no asset id - this is just a "change" output)
function (cb) {
ce.getOutput(txXfer3, 3, function (err, data) {
expect(err).to.not.exist;
expect(data.assetId).to.be.null;
expect(data.assetQuantity).to.be.null;
data.value.should.equal(58800);
data.outputType.should.equal(OutputType.TRANSFER);
expect(data.toString()).to.equal(
'TransactionOutput(value=58800, script=0x76a914d717483b5554670550f8e79a3b958d294ecf806088ac, assetId=null, assetQuantity=null, outputType=TRANSFER)');
cb();
});
}
],function () {done();});
});
});
describe('::hashScript', function () {
it('should compute the RIPEMD160 of the SHA256 of the data', function (done) {
var data = new Buffer('18E14A7B6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725','hex');
var hash = new ColoringEngine().hashScript(data);
hash.toString('hex').should.equal('29a4be50be7ff6ea37b9dcc1aa9642ab928c6dcb');
done();
});
});
describe('::addressFromBitcoinAddress', function () {
it('should compute the OpenAsset address from a Bitcoin public address', function (done) {
var btcAddr = '16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM';
var oaAddr = new ColoringEngine().addressFromBitcoinAddress(btcAddr);
oaAddr.should.equal('akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy');
done();
});
});
});