diff --git a/.babelrc b/.babelrc index 034673a6..5fb0a134 100644 --- a/.babelrc +++ b/.babelrc @@ -1,9 +1,5 @@ { - "presets": [ - "@ava/stage-4", - "@ava/transform-test-files", - "es2015-no-commonjs" - ], + "presets": ["env"], "plugins": [ "transform-export-extensions", @@ -11,21 +7,4 @@ "transform-object-rest-spread" ], "sourceMaps": true, - - "env": { - "bundle": { - "plugins": [ - ["transform-runtime", { - "polyfill": true, - "regenerator": false - }] - ] - }, - "cjs": { - "plugins": [ - "add-module-exports", - "transform-es2015-modules-commonjs" - ] - } - } } diff --git a/.ci/travis-before-install.sh b/.ci/travis-before-install.sh new file mode 100755 index 00000000..49b639fc --- /dev/null +++ b/.ci/travis-before-install.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +sudo apt-get update +sudo apt-get -y -o Dpkg::Options::="--force-confnew" install docker-ce + +sudo rm /usr/local/bin/docker-compose +curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose +chmod +x docker-compose +sudo mv docker-compose /usr/local/bin diff --git a/.ci/travis-before-script.sh b/.ci/travis-before-script.sh new file mode 100755 index 00000000..84c14d51 --- /dev/null +++ b/.ci/travis-before-script.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +set -e -x + +docker-compose up -d bigchaindb diff --git a/.ci/travis-install.sh b/.ci/travis-install.sh new file mode 100755 index 00000000..32165a84 --- /dev/null +++ b/.ci/travis-install.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +set -e -x + +docker-compose build --no-cache bigchaindb diff --git a/.ci/travis_script.sh b/.ci/travis_script.sh new file mode 100755 index 00000000..0db146a7 --- /dev/null +++ b/.ci/travis_script.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +set -e -x + +docker-compose run --rm js-bigchaindb-driver yarn test diff --git a/.travis.yml b/.travis.yml index 7d4f5cc1..985ae691 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,17 +11,20 @@ cache: directories: - node_modules +env: + global: + - DOCKER_COMPOSE_VERSION=1.19.0 + before_install: - - docker run -d -p 27017:27017 mongo:3.4 --replSet=bigchain-rs - - docker run -d -p 9984:9984 - -e BIGCHAINDB_KEYPAIR_PUBLIC=8wHUvvraRo5yEoJAt66UTZaFq9YZ9tFFwcauKPDtjkGw - -e BIGCHAINDB_KEYPAIR_PRIVATE=5C5Cknco7YxBRP9AgB1cbUVTL4FAcooxErLygw1DeG2D - -e BIGCHAINDB_DATABASE_BACKEND=mongodb - -e BIGCHAINDB_DATABASE_HOST=172.17.0.1 - bigchaindb/bigchaindb:1.3.0 - start - - gem install cowsay - - npm install -g codecov + - .ci/travis-before-install.sh + +install: + - .ci/travis-install.sh + +before_script: + - .ci/travis-before-script.sh + - gem install cowsay + - npm install -g codecov script: yarn test diff --git a/compose/Dockerfile b/compose/Dockerfile new file mode 100644 index 00000000..9be1e5fc --- /dev/null +++ b/compose/Dockerfile @@ -0,0 +1,12 @@ +FROM python:3.6 + +RUN apt-get update && apt-get install -y vim + +RUN mkdir -p /usr/src/app +WORKDIR /usr/src/app + +RUN pip install --upgrade pip ipdb ipython + +COPY . /usr/src/app/ + +RUN pip install git+https://github.com/bigchaindb/bigchaindb.git diff --git a/compose/tendermint/tmdata/config.toml b/compose/tendermint/tmdata/config.toml new file mode 100644 index 00000000..c0f493c0 --- /dev/null +++ b/compose/tendermint/tmdata/config.toml @@ -0,0 +1,17 @@ +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +proxy_app = "tcp://bigchaindb:46658" +moniker = "anonymous" +fast_sync = true +db_backend = "leveldb" +log_level = "state:debug,*:error" + +[consensus] +create_empty_blocks = false + +[rpc] +laddr = "tcp://0.0.0.0:46657" + +[p2p] +laddr = "tcp://0.0.0.0:46656" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..d55caea9 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,39 @@ +version: '2.1' + +services: + mongodb: + image: mongo:3.4.13 + ports: + - "27017" + command: mongod + bigchaindb: + depends_on: + - mongodb + - tendermint + image: bigchaindb/bigchaindb:master + environment: + BIGCHAINDB_DATABASE_HOST: mongodb + BIGCHAINDB_DATABASE_PORT: 27017 + BIGCHAINDB_SERVER_BIND: 0.0.0.0:9984 + BIGCHAINDB_WSSERVER_HOST: 0.0.0.0 + BIGCHAINDB_TENDERMINT_HOST: tendermint + BIGCHAINDB_TENDERMINT_PORT: 46657 + ports: + - "9984:9984" + - "9985:9985" + - "46658" + healthcheck: + test: ["CMD", "bash", "-c", "curl http://bigchaindb:9984 && curl http://tendermint:46657/abci_query"] + interval: 3s + timeout: 5s + retries: 3 + command: -l DEBUG start + tendermint: + image: tendermint/tendermint:0.12 + volumes: + - ./compose/tendermint/tmdata/config.toml:/tendermint/config.toml + entrypoint: '' + ports: + - "46656" + - "46657" + command: bash -c "tendermint init && tendermint node" diff --git a/docs/source/readme.rst b/docs/source/readme.rst index 4ecc5a74..97278cd2 100644 --- a/docs/source/readme.rst +++ b/docs/source/readme.rst @@ -36,10 +36,33 @@ Compatibility Matrix +-----------------------+----------------------------------+ | ``1.3`` | ``3.x.x`` | +-----------------------+----------------------------------+ +| ``2.0`` | ``4.x.x`` | ++-----------------------+----------------------------------+ + Older versions -------------------- + +#### Versions 4.x.x + + + +As part of the changes in the BigchainDB 2.0 server, some endpoint were +modified. In order to be consistent with them, the JS driver does not have +anymore the `pollStatusAndFetchTransaction()` method as there are three +different ways of posting a transaction. +- `async` using the `postTransaction`: the response will return immediately and not wait to see if the transaction is valid. +- `sync` using the `postTransactionSync`: the response will return after the transaction is validated. +- `commit` using the `postTransactionCommit`: the response will return after the transaction is committed to a block. + +By default in the docs we will use the `postTransactionCommit` as is way of +being sure that the transaction is validated and commited to a block, so +there will not be any issue if you try to transfer the asset immediately. + + +#### Versions 3.2.x + For versions below 3.2, a transfer transaction looked like: .. code-block:: js @@ -72,7 +95,7 @@ transaction should be now: The upgrade allows to create transfer transaction spending outputs that belong -to different transactions. So for instance is now possible to create a transfer +to different transactions. So for instance is now possible to create a transfer transaction spending two outputs from two different create transactions: diff --git a/docs/source/usage.rst b/docs/source/usage.rst index cba2a574..84b7bcf4 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -128,7 +128,7 @@ And sent over to a BigchainDB node: .. code-block:: js - conn.postTransaction(txCreateAliceSimpleSigned) + conn.postTransactionCommit(txCreateAliceSimpleSigned) Notice the transaction ``id``: @@ -136,22 +136,6 @@ Notice the transaction ``id``: txid = txCreateAliceSimpleSigned.id -To check the status of the transaction: - -.. code-block:: js - - conn.getStatus(txCreateAliceSimpleSigned.id) - -It is also possible to check the status every 0.5 seconds -with use of the transaction ``id``: - -.. code-block:: js - - conn.pollStatusAndFetchTransaction(txCreateAliceSimpleSigned.id) - -.. note:: It may take a small amount of time before a BigchainDB cluster - confirms a transaction as being valid. - Asset Transfer -------------- @@ -198,13 +182,10 @@ And sent over to a BigchainDB node: .. code-block:: js - conn.postTransaction(txTransferBobSigned) + conn.postTransactionCommit(txTransferBobSigned) Check the status again: -.. code-block:: js - - conn.pollStatusAndFetchTransaction(txTransferBobSigned.id) Bob is the new owner: @@ -362,15 +343,10 @@ Recap: Asset Creation & Transfer // Send the transaction off to BigchainDB const conn = new driver.Connection(API_PATH) - conn.postTransaction(txCreateAliceSimpleSigned) - // Check status of transaction every 0.5 seconds until fulfilled - .then(() => conn.pollStatusAndFetchTransaction(txCreateAliceSimpleSigned.id)) + conn.postTransactionCommit(txCreateAliceSimpleSigned) .then(retrievedTx => console.log('Transaction', retrievedTx.id, 'successfully posted.')) - // Check status after transaction has completed (result: { 'status': 'valid' }) - // If you check the status of a transaction to fast without polling, - // It returns that the transaction is waiting in the 'backlog' - .then(() => conn.getStatus(txCreateAliceSimpleSigned.id)) - .then(status => console.log('Retrieved status method 2: ', status)) + // With the postTransactionCommit if the response is correct, then the transaction + // is valid and commited to a block // Transfer bicycle to Bob .then(() => { @@ -386,12 +362,12 @@ Recap: Asset Creation & Transfer let txTransferBobSigned = driver.Transaction.signTransaction(txTransferBob, alice.privateKey) console.log('Posting signed transaction: ', txTransferBobSigned) - // Post and poll status - return conn.postTransaction(txTransferBobSigned) + // Post with commit so transaction is validated and included in a block + return conn.postTransactionCommit(txTransferBobSigned) }) .then(res => { console.log('Response from BDB server:', res) - return conn.pollStatusAndFetchTransaction(res.id) + return res.id }) .then(tx => { console.log('Is Bob the owner?', tx['outputs'][0]['public_keys'][0] == bob.publicKey) @@ -608,9 +584,7 @@ and further we transfer it from Bob to Chris. Expectations: const txCreateAliceSimpleSigned = driver.Transaction.signTransaction(txCreateAliceSimple, alice.privateKey) console.log('\n\nPosting signed create transaction for Alice:\n', txCreateAliceSimpleSigned) - conn.postTransaction(txCreateAliceSimpleSigned) - // Check status of transaction every 0.5 seconds until fulfilled - .then(() => conn.pollStatusAndFetchTransaction(txCreateAliceSimpleSigned.id)) + conn.postTransactionCommit(txCreateAliceSimpleSigned) // Transfer bicycle from Alice to Bob .then(() => { @@ -624,10 +598,9 @@ and further we transfer it from Bob to Chris. Expectations: txTransferBobSigned = driver.Transaction.signTransaction(txTransferBob, alice.privateKey) console.log('\n\nPosting signed transaction to Bob:\n', txTransferBobSigned) - // Post and poll status - return conn.postTransaction(txTransferBobSigned) + // Post with commit so transaction is validated and included in a block + return conn.postTransactionCommit(txTransferBobSigned) }) - .then(res => conn.pollStatusAndFetchTransaction(res.id)) // Second transfer of bicycle from Bob to Chris .then(tx => { @@ -641,10 +614,9 @@ and further we transfer it from Bob to Chris. Expectations: let txTransferChrisSigned = driver.Transaction.signTransaction(txTransferChris, bob.privateKey) console.log('\n\nPosting signed transaction to Chris:\n', txTransferChrisSigned) - // Post and poll status - return conn.postTransaction(txTransferChrisSigned) + // Post with commit so transaction is validated and included in a block + return conn.postTransactionCommit(txTransferChrisSigned) }) - .then(res => conn.pollStatusAndFetchTransaction(res.id)) .then(() => conn.listOutputs(alice.publicKey, true)) .then(listSpentOutputs => { console.log("\nSpent outputs for Alice: ", listSpentOutputs.length) // Spent outputs: 1 diff --git a/package.json b/package.json index 75a0955b..6e00ca94 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bigchaindb-driver", - "version": "3.2.0", + "version": "4.0.0", "description": "Node.js driver for BigchainDB", "homepage": "https://www.bigchaindb.com/", "bugs": "https://github.com/bigchaindb/js-bigchaindb-driver/issues", @@ -20,6 +20,7 @@ "build:dist": "cross-env NODE_ENV=production webpack -p", "clean": "rimraf dist/bundle dist/node", "test": "npm run lint && nyc ava test/ && npm run thanks && npm run report-coverage", + "testmine": "npm run lint && nyc ava test/integration/test_min*", "thanks": "cowsay Hi, thanks for your interest in BigchainDB. We appreciate your contribution!", "release": "./node_modules/release-it/bin/release-it.js --src.tagName='v%s' --github.release --npm.publish --non-interactive", "release-minor": "./node_modules/release-it/bin/release-it.js minor --src.tagName='v%s' --github.release --npm.publish --non-interactive", @@ -35,46 +36,47 @@ }, "devDependencies": { "ava": "^0.25.0", - "babel-cli": "^6.22.2", - "babel-eslint": "^8.0.0", - "babel-loader": "^7.0.0", + "babel-cli": "^6.26.0", + "babel-eslint": "^8.2.2", + "babel-loader": "^7.1.4", "babel-plugin-add-module-exports": "^0.2.1", - "babel-plugin-transform-es2015-modules-commonjs": "^6.23.0", + "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0", "babel-plugin-transform-export-extensions": "^6.22.0", "babel-plugin-transform-object-assign": "^6.22.0", - "babel-plugin-transform-object-rest-spread": "^6.23.0", - "babel-plugin-transform-runtime": "^6.23.0", + "babel-plugin-transform-object-rest-spread": "^6.26.0", + "babel-plugin-transform-runtime": "^6.22.0", + "babel-preset-env": "^1.6.1", "babel-preset-es2015-no-commonjs": "0.0.2", - "babel-preset-latest": "^6.22.0", - "babel-runtime": "^6.22.0", - "cross-env": "^5.0.1", - "eslint": "^4.1.1", - "eslint-config-ascribe": "^3.0.4", - "eslint-plugin-import": "^2.2.0", - "husky": "^0.14.0", + "babel-runtime": "^6.26.0", + "cross-env": "^5.1.4", + "eslint": "^4.19.1", + "eslint-config-ascribe": "^3.0.5", + "eslint-plugin-import": "^2.9.0", + "husky": "^0.14.3", "lint-staged": "^7.0.0", - "nyc": "^11.0.2", - "release-it": "^7.0.0", - "rimraf": "^2.5.4", - "sinon": "^4.0.0", - "webpack": "^4.0.0" + "nyc": "^11.6.0", + "release-it": "^7.2.1", + "rimraf": "^2.6.2", + "sinon": "^4.4.9", + "webpack": "^4.4.1", + "webpack-cli": "^2.0.13" }, "dependencies": { "browser-resolve": "^1.11.2", - "bs58": "^4.0.0", - "buffer": "^5.0.2", - "clone": "^2.1.0", - "core-js": "^2.4.1", + "bs58": "^4.0.1", + "buffer": "^5.1.0", + "clone": "^2.1.1", + "core-js": "^2.5.4", "decamelize": "^2.0.0", - "es6-promise": "^4.0.5", - "fetch-ponyfill": "^6.0.0", + "es6-promise": "^4.2.4", + "fetch-ponyfill": "^6.0.1", "crypto-conditions": "^2.0.1", "isomorphic-fetch": "^2.2.1", "js-sha3": "^0.7.0", "js-utility-belt": "^1.5.0", "json-stable-stringify": "^1.0.1", - "query-string": "^5.0.0", - "sprintf-js": "^1.0.3", + "query-string": "^6.0.0", + "sprintf-js": "^1.1.1", "tweetnacl": "^1.0.0" }, "keywords": [ diff --git a/src/connection.js b/src/connection.js index 6e004951..75f391b8 100644 --- a/src/connection.js +++ b/src/connection.js @@ -19,10 +19,11 @@ export default class Connection { getApiUrls(endpoint) { return this.path + { 'blocks': 'blocks', - 'blocksDetail': 'blocks/%(blockId)s', + 'blocksDetail': 'blocks/%(blockHeight)s', 'outputs': 'outputs', - 'statuses': 'statuses', 'transactions': 'transactions', + 'transactionsSync': 'transactions?mode=sync', + 'transactionsCommit': 'transactions?mode=commit', 'transactionsDetail': 'transactions/%(transactionId)s', 'assets': 'assets', 'metadata': 'metadata', @@ -38,24 +39,12 @@ export default class Connection { /** * @public - * @param blockId + * @param blockHeight */ - getBlock(blockId) { + getBlock(blockHeight) { return this._req(this.getApiUrls('blocksDetail'), { urlTemplateSpec: { - blockId - } - }) - } - - /** - * @public - * @param transactionId - */ - getStatus(transactionId) { - return this._req(this.getApiUrls('statuses'), { - query: { - transaction_id: transactionId + blockHeight } }) } @@ -77,11 +66,10 @@ export default class Connection { * @param transactionId * @param status */ - listBlocks(transactionId, status) { + listBlocks(transactionId) { return this._req(this.getApiUrls('blocks'), { query: { transaction_id: transactionId, - status } }) } @@ -133,27 +121,12 @@ export default class Connection { /** * @public - * @param txId - * @return {Promise} + * @param transaction */ - pollStatusAndFetchTransaction(txId) { - return new Promise((resolve, reject) => { - const timer = setInterval(() => { - this.getStatus(txId) - .then((res) => { - if (res.status === 'valid') { - clearInterval(timer) - this.getTransaction(txId) - .then((res_) => { - resolve(res_) - }) - } - }) - .catch((err) => { - clearInterval(timer) - reject(err) - }) - }, 500) + postTransaction(transaction) { + return this._req(this.getApiUrls('transactions'), { + method: 'POST', + jsonBody: transaction }) } @@ -161,8 +134,20 @@ export default class Connection { * @public * @param transaction */ - postTransaction(transaction) { - return this._req(this.getApiUrls('transactions'), { + postTransactionSync(transaction) { + return this._req(this.getApiUrls('transactionsSync'), { + method: 'POST', + jsonBody: transaction + }) + } + + /** + * @public + * @param transaction + */ + postTransactionCommit(transaction) { + return this._req(this.getApiUrls('transactionsCommit'), { + method: 'POST', jsonBody: transaction }) diff --git a/src/transaction.js b/src/transaction.js index f84cf4c9..d64e7ddb 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -30,14 +30,6 @@ export default class Transaction { } } - static hashTransaction(transaction) { - // Safely remove any tx id from the given transaction for hashing - const tx = { ...transaction } - delete tx.id - - return sha256Hash(Transaction.serializeTransactionIntoCanonicalString(tx)) - } - static makeTransactionTemplate() { const txTemplate = { 'id': null, @@ -46,7 +38,7 @@ export default class Transaction { 'inputs': [], 'metadata': null, 'asset': null, - 'version': '1.0', + 'version': '2.0', } return txTemplate } @@ -58,8 +50,6 @@ export default class Transaction { tx.metadata = metadata tx.inputs = inputs tx.outputs = outputs - - tx.id = Transaction.hashTransaction(tx) return tx } @@ -246,15 +236,23 @@ export default class Transaction { signedTx.inputs.forEach((input, index) => { const privateKey = privateKeys[index] const privateKeyBuffer = Buffer.from(base58.decode(privateKey)) - const serializedTransaction = Transaction - .serializeTransactionIntoCanonicalString(transaction) + const serializedTransaction = + Transaction.serializeTransactionIntoCanonicalString(transaction) + + const transactionUniqueFulfillment = input.fulfills ? serializedTransaction + .concat(input.fulfills.transaction_id) + .concat(input.fulfills.output_index) : serializedTransaction + const transactionHash = sha256Hash(transactionUniqueFulfillment) const ed25519Fulfillment = new cc.Ed25519Sha256() - ed25519Fulfillment.sign(Buffer.from(serializedTransaction), privateKeyBuffer) + ed25519Fulfillment.sign(Buffer.from(transactionHash, 'hex'), privateKeyBuffer) const fulfillmentUri = ed25519Fulfillment.serializeUri() input.fulfillment = fulfillmentUri }) + const serializedTransaction = + Transaction.serializeTransactionIntoCanonicalString(signedTx) + signedTx.id = sha256Hash(serializedTransaction) return signedTx } } diff --git a/test/connection/test_connection.js b/test/connection/test_connection.js index 58775634..960c11f2 100644 --- a/test/connection/test_connection.js +++ b/test/connection/test_connection.js @@ -3,8 +3,8 @@ import sinon from 'sinon' import * as request from '../../src/request' // eslint-disable-line import { Connection } from '../../src' +import { API_PATH } from '../constants' -const API_PATH = 'http://localhost:9984/api/v1/' const conn = new Connection(API_PATH) test('Payload thrown at incorrect API_PATH', t => { @@ -24,9 +24,8 @@ test('Payload thrown at incorrect API_PATH', t => { test('Generate API URLS', t => { const endpoints = { 'blocks': 'blocks', - 'blocksDetail': 'blocks/%(blockId)s', + 'blocksDetail': 'blocks/%(blockHeight)s', 'outputs': 'outputs', - 'statuses': 'statuses', 'transactions': 'transactions', 'transactionsDetail': 'transactions/%(transactionId)s', 'assets': 'assets', @@ -59,30 +58,15 @@ test('Request with custom headers', t => { test('Get block for a block id', t => { const expectedPath = 'path' - const blockId = 'abc' - - conn._req = sinon.spy() - conn.getApiUrls = sinon.stub().returns(expectedPath) - - conn.getBlock(blockId) - t.truthy(conn._req.calledWith( - expectedPath, - { urlTemplateSpec: { blockId } } - )) -}) - - -test('Get status for a transaction id', t => { - const expectedPath = 'path' - const transactionId = 'abc' + const blockHeight = 'abc' conn._req = sinon.spy() conn.getApiUrls = sinon.stub().returns(expectedPath) - conn.getStatus(transactionId) + conn.getBlock(blockHeight) t.truthy(conn._req.calledWith( expectedPath, - { query: { transaction_id: transactionId } } + { urlTemplateSpec: { blockHeight } } )) }) @@ -105,18 +89,16 @@ test('Get transaction for a transaction id', t => { test('Get list of blocks for a transaction id', t => { const expectedPath = 'path' const transactionId = 'abc' - const status = 'status' conn._req = sinon.spy() conn.getApiUrls = sinon.stub().returns(expectedPath) - conn.listBlocks(transactionId, status) + conn.listBlocks(transactionId) t.truthy(conn._req.calledWith( expectedPath, { query: { transaction_id: transactionId, - status } } )) diff --git a/test/constants.js b/test/constants.js index bcd9b5de..2194151e 100644 --- a/test/constants.js +++ b/test/constants.js @@ -4,6 +4,7 @@ import { Transaction, Ed25519Keypair } from '../src' // NOTE: It's safer to cast `Math.random()` to a string, to avoid differences // in "float interpretation" between languages (e.g. JavaScript and Python) +export const API_PATH = 'http://localhost:9984/api/v1/' export function asset() { return { message: `${Math.random()}` } } export const metaData = { message: 'metaDataMessage' } diff --git a/test/integration/test_integration.js b/test/integration/test_integration.js index 255ab55c..b416fec9 100644 --- a/test/integration/test_integration.js +++ b/test/integration/test_integration.js @@ -2,6 +2,7 @@ import test from 'ava' import { Ed25519Keypair, Transaction, Connection } from '../../src' import { + API_PATH, alice, aliceCondition, aliceOutput, @@ -11,8 +12,6 @@ import { metaData } from '../constants' -const API_PATH = 'http://localhost:9984/api/v1/' - test('Keypair is created', t => { const keyPair = new Ed25519Keypair() @@ -36,8 +35,7 @@ test('Valid CREATE transaction', t => { ) const txSigned = Transaction.signTransaction(tx, alice.privateKey) - return conn.postTransaction(txSigned) - .then(({ id }) => conn.pollStatusAndFetchTransaction(id)) + return conn.postTransactionCommit(txSigned) .then(resTx => t.truthy(resTx)) }) @@ -55,8 +53,7 @@ test('Valid TRANSFER transaction with single Ed25519 input', t => { alice.privateKey ) - return conn.postTransaction(createTxSigned) - .then(({ id }) => conn.pollStatusAndFetchTransaction(id)) + return conn.postTransactionCommit(createTxSigned) .then(() => { const transferTx = Transaction.makeTransferTransaction( [{ tx: createTxSigned, output_index: 0 }], @@ -67,8 +64,7 @@ test('Valid TRANSFER transaction with single Ed25519 input', t => { transferTx, alice.privateKey ) - return conn.postTransaction(transferTxSigned) - .then(({ id }) => conn.pollStatusAndFetchTransaction(id)) + return conn.postTransactionCommit(transferTxSigned) .then(resTx => t.truthy(resTx)) }) }) @@ -87,8 +83,7 @@ test('Valid TRANSFER transaction with multiple Ed25519 inputs', t => { alice.privateKey ) - return conn.postTransaction(createTxSigned) - .then(({ 'id': txId }) => conn.pollStatusAndFetchTransaction(txId)) + return conn.postTransactionCommit(createTxSigned) .then(() => { const transferTx = Transaction.makeTransferTransaction( [{ tx: createTxSigned, output_index: 0 }, { tx: createTxSigned, output_index: 1 }], @@ -100,8 +95,7 @@ test('Valid TRANSFER transaction with multiple Ed25519 inputs', t => { alice.privateKey, bob.privateKey ) - return conn.postTransaction(transferTxSigned) - .then(({ id }) => conn.pollStatusAndFetchTransaction(id)) + return conn.postTransactionCommit(transferTxSigned) .then(resTx => t.truthy(resTx)) }) }) @@ -129,8 +123,7 @@ test('Valid TRANSFER transaction with multiple Ed25519 inputs from different tra alice.privateKey ) - return conn.postTransaction(createTxSigned) - .then(({ 'id': txId }) => conn.pollStatusAndFetchTransaction(txId)) + return conn.postTransactionCommit(createTxSigned) .then(() => { const transferTx1 = Transaction.makeTransferTransaction( [{ tx: createTxSigned, output_index: 0 }], @@ -151,10 +144,8 @@ test('Valid TRANSFER transaction with multiple Ed25519 inputs from different tra bob.privateKey ) - return conn.postTransaction(transferTxSigned1) - .then(({ id }) => conn.pollStatusAndFetchTransaction(id)) - .then(conn.postTransaction(transferTxSigned2)) - .then(({ id }) => conn.pollStatusAndFetchTransaction(id)) + return conn.postTransactionCommit(transferTxSigned1) + .then(conn.postTransactionCommit(transferTxSigned2)) .then(() => { const transferTxMultipleInputs = Transaction.makeTransferTransaction( [{ tx: transferTxSigned1, output_index: 0 }, @@ -167,14 +158,12 @@ test('Valid TRANSFER transaction with multiple Ed25519 inputs from different tra carol.privateKey, trent.privateKey ) - return conn.postTransaction(transferTxSignedMultipleInputs) - .then(({ id }) => conn.pollStatusAndFetchTransaction(id)) + return conn.postTransactionCommit(transferTxSignedMultipleInputs) .then(resTx => t.truthy(resTx)) }) }) }) - test('Search for spent and unspent outputs of a given public key', t => { const conn = new Connection(API_PATH) const carol = new Ed25519Keypair() @@ -208,10 +197,8 @@ test('Search for spent and unspent outputs of a given public key', t => { carol.privateKey, ) - return conn.postTransaction(createTxSigned) - .then(({ id }) => conn.pollStatusAndFetchTransaction(id)) - .then(() => conn.postTransaction(transferTxSigned)) - .then(({ id }) => conn.pollStatusAndFetchTransaction(id)) + return conn.postTransactionCommit(createTxSigned) + .then(() => conn.postTransactionCommit(transferTxSigned)) .then(() => conn.listOutputs(carol.publicKey)) // now listOutputs should return us outputs 0 and 1 (unfiltered) .then(outputs => t.truthy(outputs.length === 2)) @@ -250,10 +237,8 @@ test('Search for unspent outputs for a given public key', t => { carol.privateKey, ) - return conn.postTransaction(createTxSigned) - .then(({ id }) => conn.pollStatusAndFetchTransaction(id)) - .then(() => conn.postTransaction(transferTxSigned)) - .then(({ id }) => conn.pollStatusAndFetchTransaction(id)) + return conn.postTransactionCommit(createTxSigned) + .then(() => conn.postTransactionCommit(transferTxSigned)) // now listOutputs should return us outputs 0 and 2 (1 is spent) .then(() => conn.listOutputs(carol.publicKey, 'false')) .then(outputs => t.truthy(outputs.length === 2)) @@ -292,10 +277,8 @@ test('Search for spent outputs for a given public key', t => { carol.privateKey, ) - return conn.postTransaction(createTxSigned) - .then(({ id }) => conn.pollStatusAndFetchTransaction(id)) - .then(() => conn.postTransaction(transferTxSigned)) - .then(({ id }) => conn.pollStatusAndFetchTransaction(id)) + return conn.postTransactionCommit(createTxSigned) + .then(() => conn.postTransactionCommit(transferTxSigned)) // now listOutputs should only return us output 1 (0 and 2 are unspent) .then(() => conn.listOutputs(carol.publicKey, true)) .then(outputs => t.truthy(outputs.length === 1)) @@ -316,8 +299,7 @@ test('Search for an asset', t => { alice.privateKey ) - return conn.postTransaction(createTxSigned) - .then(({ id }) => conn.pollStatusAndFetchTransaction(id)) + return conn.postTransactionCommit(createTxSigned) .then(() => conn.searchAssets(createTxSigned.asset.data.message)) .then(assets => t.truthy( assets.pop(), @@ -340,8 +322,7 @@ test('Search for metadata', t => { alice.privateKey ) - return conn.postTransaction(createTxSigned) - .then(({ id }) => conn.pollStatusAndFetchTransaction(id)) + return conn.postTransactionCommit(createTxSigned) .then(() => conn.searchMetadata(createTxSigned.metadata.message)) .then(assets => t.truthy( assets.pop(), @@ -349,6 +330,7 @@ test('Search for metadata', t => { )) }) + test('Search blocks containing a transaction', t => { const conn = new Connection(API_PATH) @@ -363,11 +345,10 @@ test('Search blocks containing a transaction', t => { alice.privateKey ) - return conn.postTransaction(createTxSigned) - .then(({ id }) => conn.pollStatusAndFetchTransaction(id)) - .then(({ id }) => conn.listBlocks(id, 'VALID')) - .then(blocks => conn.getBlock(blocks.pop())) - .then(({ block: { transactions } }) => transactions.filter(({ id }) => id === createTxSigned.id)) + return conn.postTransactionCommit(createTxSigned) + .then(({ id }) => conn.listBlocks(id)) + .then(blockHeight => conn.getBlock(blockHeight.pop())) + .then(({ transactions }) => transactions.filter(({ id }) => id === createTxSigned.id)) .then(transactions => t.truthy(transactions.length === 1)) }) @@ -386,10 +367,11 @@ test('Search transaction containing an asset', t => { alice.privateKey ) - return conn.postTransaction(createTxSigned) - .then(({ id }) => conn.pollStatusAndFetchTransaction(id, 'CREATE')) + return conn.postTransactionCommit(createTxSigned) .then(({ id }) => conn.listTransactions(id)) - .then(transactions => t.truthy(transactions.length === 1)) + .then(transactions => { + t.truthy(transactions.length === 1) + }) }) diff --git a/test/transaction/test_cryptoconditions.js b/test/transaction/test_cryptoconditions.js index 4673e17f..65e389b4 100644 --- a/test/transaction/test_cryptoconditions.js +++ b/test/transaction/test_cryptoconditions.js @@ -1,6 +1,7 @@ import test from 'ava' import cc from 'crypto-conditions' import { Ed25519Keypair, Transaction, ccJsonLoad } from '../../src' +import sha256Hash from '../../src/sha256Hash' test('Ed25519 condition encoding', t => { const publicKey = '4zvwRjXUKGfvwnParsHAS3HuSVzV5cA4McphgmoCtajS' @@ -53,16 +54,27 @@ test('Fulfillment correctly formed', t => { [Transaction.makeOutput(Transaction.makeEd25519Condition(alice.publicKey))], alice.publicKey ) + // Sign in order to get the tx id, needed for the unique fulfillment in the transfer transaction + const signCreateTransaction = Transaction.signTransaction(txCreate, alice.privateKey) + const txTransfer = Transaction.makeTransferTransaction( - [{ tx: txCreate, output_index: 0 }], + [{ tx: signCreateTransaction, output_index: 0 }], [Transaction.makeOutput(Transaction.makeEd25519Condition(alice.publicKey))], {} ) - const msg = Transaction.serializeTransactionIntoCanonicalString(txTransfer) const txSigned = Transaction.signTransaction(txTransfer, alice.privateKey) + + // Here reconstruct the fulfillment of the transfer transaction + // The tx is serialized, and extended with tx_id and output index, and then hashed into bytes + const msg = Transaction.serializeTransactionIntoCanonicalString(txTransfer) + const msgUniqueFulfillment = txTransfer.inputs[0].fulfills ? msg + .concat(txTransfer.inputs[0].fulfills.transaction_id) + .concat(txTransfer.inputs[0].fulfills.output_index) : msg + const msgHash = sha256Hash(msgUniqueFulfillment) + t.truthy(cc.validateFulfillment( txSigned.inputs[0].fulfillment, txCreate.outputs[0].condition.uri, - Buffer.from(msg) + Buffer.from(msgHash, 'hex') )) }) diff --git a/webpack.config.js b/webpack.config.js index 103aee7b..b7d1ad55 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -54,6 +54,7 @@ const PLUGINS = [ ] const PROD_PLUGINS = [ + /* new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false, @@ -67,6 +68,7 @@ const PROD_PLUGINS = [ debug: false, minimize: true, }), + */ ] if (PRODUCTION) {