1
0
mirror of https://github.com/bigchaindb/js-bigchaindb-driver.git synced 2024-11-22 09:46:58 +01:00

Merge pull request #159 from bigchaindb/tendermint-tx-fixed

Update driver in order to be compatible with BigchainDB2.0
This commit is contained in:
Manolo 2018-03-29 19:29:56 +02:00 committed by GitHub
commit 618c2526f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 261 additions and 228 deletions

View File

@ -1,9 +1,5 @@
{ {
"presets": [ "presets": ["env"],
"@ava/stage-4",
"@ava/transform-test-files",
"es2015-no-commonjs"
],
"plugins": [ "plugins": [
"transform-export-extensions", "transform-export-extensions",
@ -11,21 +7,4 @@
"transform-object-rest-spread" "transform-object-rest-spread"
], ],
"sourceMaps": true, "sourceMaps": true,
"env": {
"bundle": {
"plugins": [
["transform-runtime", {
"polyfill": true,
"regenerator": false
}]
]
},
"cjs": {
"plugins": [
"add-module-exports",
"transform-es2015-modules-commonjs"
]
}
}
} }

9
.ci/travis-before-install.sh Executable file
View File

@ -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

5
.ci/travis-before-script.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
set -e -x
docker-compose up -d bigchaindb

5
.ci/travis-install.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
set -e -x
docker-compose build --no-cache bigchaindb

5
.ci/travis_script.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
set -e -x
docker-compose run --rm js-bigchaindb-driver yarn test

View File

@ -11,15 +11,18 @@ cache:
directories: directories:
- node_modules - node_modules
env:
global:
- DOCKER_COMPOSE_VERSION=1.19.0
before_install: before_install:
- docker run -d -p 27017:27017 mongo:3.4 --replSet=bigchain-rs - .ci/travis-before-install.sh
- docker run -d -p 9984:9984
-e BIGCHAINDB_KEYPAIR_PUBLIC=8wHUvvraRo5yEoJAt66UTZaFq9YZ9tFFwcauKPDtjkGw install:
-e BIGCHAINDB_KEYPAIR_PRIVATE=5C5Cknco7YxBRP9AgB1cbUVTL4FAcooxErLygw1DeG2D - .ci/travis-install.sh
-e BIGCHAINDB_DATABASE_BACKEND=mongodb
-e BIGCHAINDB_DATABASE_HOST=172.17.0.1 before_script:
bigchaindb/bigchaindb:1.3.0 - .ci/travis-before-script.sh
start
- gem install cowsay - gem install cowsay
- npm install -g codecov - npm install -g codecov

12
compose/Dockerfile Normal file
View File

@ -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

View File

@ -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"

39
docker-compose.yml Normal file
View File

@ -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"

View File

@ -36,10 +36,33 @@ Compatibility Matrix
+-----------------------+----------------------------------+ +-----------------------+----------------------------------+
| ``1.3`` | ``3.x.x`` | | ``1.3`` | ``3.x.x`` |
+-----------------------+----------------------------------+ +-----------------------+----------------------------------+
| ``2.0`` | ``4.x.x`` |
+-----------------------+----------------------------------+
Older versions 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: For versions below 3.2, a transfer transaction looked like:
.. code-block:: js .. code-block:: js

View File

@ -128,7 +128,7 @@ And sent over to a BigchainDB node:
.. code-block:: js .. code-block:: js
conn.postTransaction(txCreateAliceSimpleSigned) conn.postTransactionCommit(txCreateAliceSimpleSigned)
Notice the transaction ``id``: Notice the transaction ``id``:
@ -136,22 +136,6 @@ Notice the transaction ``id``:
txid = txCreateAliceSimpleSigned.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 Asset Transfer
-------------- --------------
@ -198,13 +182,10 @@ And sent over to a BigchainDB node:
.. code-block:: js .. code-block:: js
conn.postTransaction(txTransferBobSigned) conn.postTransactionCommit(txTransferBobSigned)
Check the status again: Check the status again:
.. code-block:: js
conn.pollStatusAndFetchTransaction(txTransferBobSigned.id)
Bob is the new owner: Bob is the new owner:
@ -362,15 +343,10 @@ Recap: Asset Creation & Transfer
// Send the transaction off to BigchainDB // Send the transaction off to BigchainDB
const conn = new driver.Connection(API_PATH) const conn = new driver.Connection(API_PATH)
conn.postTransaction(txCreateAliceSimpleSigned) conn.postTransactionCommit(txCreateAliceSimpleSigned)
// Check status of transaction every 0.5 seconds until fulfilled
.then(() => conn.pollStatusAndFetchTransaction(txCreateAliceSimpleSigned.id))
.then(retrievedTx => console.log('Transaction', retrievedTx.id, 'successfully posted.')) .then(retrievedTx => console.log('Transaction', retrievedTx.id, 'successfully posted.'))
// Check status after transaction has completed (result: { 'status': 'valid' }) // With the postTransactionCommit if the response is correct, then the transaction
// If you check the status of a transaction to fast without polling, // is valid and commited to a block
// It returns that the transaction is waiting in the 'backlog'
.then(() => conn.getStatus(txCreateAliceSimpleSigned.id))
.then(status => console.log('Retrieved status method 2: ', status))
// Transfer bicycle to Bob // Transfer bicycle to Bob
.then(() => { .then(() => {
@ -386,12 +362,12 @@ Recap: Asset Creation & Transfer
let txTransferBobSigned = driver.Transaction.signTransaction(txTransferBob, alice.privateKey) let txTransferBobSigned = driver.Transaction.signTransaction(txTransferBob, alice.privateKey)
console.log('Posting signed transaction: ', txTransferBobSigned) console.log('Posting signed transaction: ', txTransferBobSigned)
// Post and poll status // Post with commit so transaction is validated and included in a block
return conn.postTransaction(txTransferBobSigned) return conn.postTransactionCommit(txTransferBobSigned)
}) })
.then(res => { .then(res => {
console.log('Response from BDB server:', res) console.log('Response from BDB server:', res)
return conn.pollStatusAndFetchTransaction(res.id) return res.id
}) })
.then(tx => { .then(tx => {
console.log('Is Bob the owner?', tx['outputs'][0]['public_keys'][0] == bob.publicKey) 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) const txCreateAliceSimpleSigned = driver.Transaction.signTransaction(txCreateAliceSimple, alice.privateKey)
console.log('\n\nPosting signed create transaction for Alice:\n', txCreateAliceSimpleSigned) console.log('\n\nPosting signed create transaction for Alice:\n', txCreateAliceSimpleSigned)
conn.postTransaction(txCreateAliceSimpleSigned) conn.postTransactionCommit(txCreateAliceSimpleSigned)
// Check status of transaction every 0.5 seconds until fulfilled
.then(() => conn.pollStatusAndFetchTransaction(txCreateAliceSimpleSigned.id))
// Transfer bicycle from Alice to Bob // Transfer bicycle from Alice to Bob
.then(() => { .then(() => {
@ -624,10 +598,9 @@ and further we transfer it from Bob to Chris. Expectations:
txTransferBobSigned = driver.Transaction.signTransaction(txTransferBob, alice.privateKey) txTransferBobSigned = driver.Transaction.signTransaction(txTransferBob, alice.privateKey)
console.log('\n\nPosting signed transaction to Bob:\n', txTransferBobSigned) console.log('\n\nPosting signed transaction to Bob:\n', txTransferBobSigned)
// Post and poll status // Post with commit so transaction is validated and included in a block
return conn.postTransaction(txTransferBobSigned) return conn.postTransactionCommit(txTransferBobSigned)
}) })
.then(res => conn.pollStatusAndFetchTransaction(res.id))
// Second transfer of bicycle from Bob to Chris // Second transfer of bicycle from Bob to Chris
.then(tx => { .then(tx => {
@ -641,10 +614,9 @@ and further we transfer it from Bob to Chris. Expectations:
let txTransferChrisSigned = driver.Transaction.signTransaction(txTransferChris, bob.privateKey) let txTransferChrisSigned = driver.Transaction.signTransaction(txTransferChris, bob.privateKey)
console.log('\n\nPosting signed transaction to Chris:\n', txTransferChrisSigned) console.log('\n\nPosting signed transaction to Chris:\n', txTransferChrisSigned)
// Post and poll status // Post with commit so transaction is validated and included in a block
return conn.postTransaction(txTransferChrisSigned) return conn.postTransactionCommit(txTransferChrisSigned)
}) })
.then(res => conn.pollStatusAndFetchTransaction(res.id))
.then(() => conn.listOutputs(alice.publicKey, true)) .then(() => conn.listOutputs(alice.publicKey, true))
.then(listSpentOutputs => { .then(listSpentOutputs => {
console.log("\nSpent outputs for Alice: ", listSpentOutputs.length) // Spent outputs: 1 console.log("\nSpent outputs for Alice: ", listSpentOutputs.length) // Spent outputs: 1

View File

@ -1,6 +1,6 @@
{ {
"name": "bigchaindb-driver", "name": "bigchaindb-driver",
"version": "3.2.0", "version": "4.0.0",
"description": "Node.js driver for BigchainDB", "description": "Node.js driver for BigchainDB",
"homepage": "https://www.bigchaindb.com/", "homepage": "https://www.bigchaindb.com/",
"bugs": "https://github.com/bigchaindb/js-bigchaindb-driver/issues", "bugs": "https://github.com/bigchaindb/js-bigchaindb-driver/issues",
@ -20,6 +20,7 @@
"build:dist": "cross-env NODE_ENV=production webpack -p", "build:dist": "cross-env NODE_ENV=production webpack -p",
"clean": "rimraf dist/bundle dist/node", "clean": "rimraf dist/bundle dist/node",
"test": "npm run lint && nyc ava test/ && npm run thanks && npm run report-coverage", "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!", "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": "./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", "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": { "devDependencies": {
"ava": "^0.25.0", "ava": "^0.25.0",
"babel-cli": "^6.22.2", "babel-cli": "^6.26.0",
"babel-eslint": "^8.0.0", "babel-eslint": "^8.2.2",
"babel-loader": "^7.0.0", "babel-loader": "^7.1.4",
"babel-plugin-add-module-exports": "^0.2.1", "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-export-extensions": "^6.22.0",
"babel-plugin-transform-object-assign": "^6.22.0", "babel-plugin-transform-object-assign": "^6.22.0",
"babel-plugin-transform-object-rest-spread": "^6.23.0", "babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-plugin-transform-runtime": "^6.23.0", "babel-plugin-transform-runtime": "^6.22.0",
"babel-preset-env": "^1.6.1",
"babel-preset-es2015-no-commonjs": "0.0.2", "babel-preset-es2015-no-commonjs": "0.0.2",
"babel-preset-latest": "^6.22.0", "babel-runtime": "^6.26.0",
"babel-runtime": "^6.22.0", "cross-env": "^5.1.4",
"cross-env": "^5.0.1", "eslint": "^4.19.1",
"eslint": "^4.1.1", "eslint-config-ascribe": "^3.0.5",
"eslint-config-ascribe": "^3.0.4", "eslint-plugin-import": "^2.9.0",
"eslint-plugin-import": "^2.2.0", "husky": "^0.14.3",
"husky": "^0.14.0",
"lint-staged": "^7.0.0", "lint-staged": "^7.0.0",
"nyc": "^11.0.2", "nyc": "^11.6.0",
"release-it": "^7.0.0", "release-it": "^7.2.1",
"rimraf": "^2.5.4", "rimraf": "^2.6.2",
"sinon": "^4.0.0", "sinon": "^4.4.9",
"webpack": "^4.0.0" "webpack": "^4.4.1",
"webpack-cli": "^2.0.13"
}, },
"dependencies": { "dependencies": {
"browser-resolve": "^1.11.2", "browser-resolve": "^1.11.2",
"bs58": "^4.0.0", "bs58": "^4.0.1",
"buffer": "^5.0.2", "buffer": "^5.1.0",
"clone": "^2.1.0", "clone": "^2.1.1",
"core-js": "^2.4.1", "core-js": "^2.5.4",
"decamelize": "^2.0.0", "decamelize": "^2.0.0",
"es6-promise": "^4.0.5", "es6-promise": "^4.2.4",
"fetch-ponyfill": "^6.0.0", "fetch-ponyfill": "^6.0.1",
"crypto-conditions": "^2.0.1", "crypto-conditions": "^2.0.1",
"isomorphic-fetch": "^2.2.1", "isomorphic-fetch": "^2.2.1",
"js-sha3": "^0.7.0", "js-sha3": "^0.7.0",
"js-utility-belt": "^1.5.0", "js-utility-belt": "^1.5.0",
"json-stable-stringify": "^1.0.1", "json-stable-stringify": "^1.0.1",
"query-string": "^5.0.0", "query-string": "^6.0.0",
"sprintf-js": "^1.0.3", "sprintf-js": "^1.1.1",
"tweetnacl": "^1.0.0" "tweetnacl": "^1.0.0"
}, },
"keywords": [ "keywords": [

View File

@ -19,10 +19,11 @@ export default class Connection {
getApiUrls(endpoint) { getApiUrls(endpoint) {
return this.path + { return this.path + {
'blocks': 'blocks', 'blocks': 'blocks',
'blocksDetail': 'blocks/%(blockId)s', 'blocksDetail': 'blocks/%(blockHeight)s',
'outputs': 'outputs', 'outputs': 'outputs',
'statuses': 'statuses',
'transactions': 'transactions', 'transactions': 'transactions',
'transactionsSync': 'transactions?mode=sync',
'transactionsCommit': 'transactions?mode=commit',
'transactionsDetail': 'transactions/%(transactionId)s', 'transactionsDetail': 'transactions/%(transactionId)s',
'assets': 'assets', 'assets': 'assets',
'metadata': 'metadata', 'metadata': 'metadata',
@ -38,24 +39,12 @@ export default class Connection {
/** /**
* @public * @public
* @param blockId * @param blockHeight
*/ */
getBlock(blockId) { getBlock(blockHeight) {
return this._req(this.getApiUrls('blocksDetail'), { return this._req(this.getApiUrls('blocksDetail'), {
urlTemplateSpec: { urlTemplateSpec: {
blockId blockHeight
}
})
}
/**
* @public
* @param transactionId
*/
getStatus(transactionId) {
return this._req(this.getApiUrls('statuses'), {
query: {
transaction_id: transactionId
} }
}) })
} }
@ -77,11 +66,10 @@ export default class Connection {
* @param transactionId * @param transactionId
* @param status * @param status
*/ */
listBlocks(transactionId, status) { listBlocks(transactionId) {
return this._req(this.getApiUrls('blocks'), { return this._req(this.getApiUrls('blocks'), {
query: { query: {
transaction_id: transactionId, transaction_id: transactionId,
status
} }
}) })
} }
@ -133,27 +121,12 @@ export default class Connection {
/** /**
* @public * @public
* @param txId * @param transaction
* @return {Promise}
*/ */
pollStatusAndFetchTransaction(txId) { postTransaction(transaction) {
return new Promise((resolve, reject) => { return this._req(this.getApiUrls('transactions'), {
const timer = setInterval(() => { method: 'POST',
this.getStatus(txId) jsonBody: transaction
.then((res) => {
if (res.status === 'valid') {
clearInterval(timer)
this.getTransaction(txId)
.then((res_) => {
resolve(res_)
})
}
})
.catch((err) => {
clearInterval(timer)
reject(err)
})
}, 500)
}) })
} }
@ -161,8 +134,20 @@ export default class Connection {
* @public * @public
* @param transaction * @param transaction
*/ */
postTransaction(transaction) { postTransactionSync(transaction) {
return this._req(this.getApiUrls('transactions'), { return this._req(this.getApiUrls('transactionsSync'), {
method: 'POST',
jsonBody: transaction
})
}
/**
* @public
* @param transaction
*/
postTransactionCommit(transaction) {
return this._req(this.getApiUrls('transactionsCommit'), {
method: 'POST', method: 'POST',
jsonBody: transaction jsonBody: transaction
}) })

View File

@ -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() { static makeTransactionTemplate() {
const txTemplate = { const txTemplate = {
'id': null, 'id': null,
@ -46,7 +38,7 @@ export default class Transaction {
'inputs': [], 'inputs': [],
'metadata': null, 'metadata': null,
'asset': null, 'asset': null,
'version': '1.0', 'version': '2.0',
} }
return txTemplate return txTemplate
} }
@ -58,8 +50,6 @@ export default class Transaction {
tx.metadata = metadata tx.metadata = metadata
tx.inputs = inputs tx.inputs = inputs
tx.outputs = outputs tx.outputs = outputs
tx.id = Transaction.hashTransaction(tx)
return tx return tx
} }
@ -246,15 +236,23 @@ export default class Transaction {
signedTx.inputs.forEach((input, index) => { signedTx.inputs.forEach((input, index) => {
const privateKey = privateKeys[index] const privateKey = privateKeys[index]
const privateKeyBuffer = Buffer.from(base58.decode(privateKey)) const privateKeyBuffer = Buffer.from(base58.decode(privateKey))
const serializedTransaction = Transaction const serializedTransaction =
.serializeTransactionIntoCanonicalString(transaction) 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() const ed25519Fulfillment = new cc.Ed25519Sha256()
ed25519Fulfillment.sign(Buffer.from(serializedTransaction), privateKeyBuffer) ed25519Fulfillment.sign(Buffer.from(transactionHash, 'hex'), privateKeyBuffer)
const fulfillmentUri = ed25519Fulfillment.serializeUri() const fulfillmentUri = ed25519Fulfillment.serializeUri()
input.fulfillment = fulfillmentUri input.fulfillment = fulfillmentUri
}) })
const serializedTransaction =
Transaction.serializeTransactionIntoCanonicalString(signedTx)
signedTx.id = sha256Hash(serializedTransaction)
return signedTx return signedTx
} }
} }

View File

@ -3,8 +3,8 @@ import sinon from 'sinon'
import * as request from '../../src/request' // eslint-disable-line import * as request from '../../src/request' // eslint-disable-line
import { Connection } from '../../src' import { Connection } from '../../src'
import { API_PATH } from '../constants'
const API_PATH = 'http://localhost:9984/api/v1/'
const conn = new Connection(API_PATH) const conn = new Connection(API_PATH)
test('Payload thrown at incorrect API_PATH', t => { 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 => { test('Generate API URLS', t => {
const endpoints = { const endpoints = {
'blocks': 'blocks', 'blocks': 'blocks',
'blocksDetail': 'blocks/%(blockId)s', 'blocksDetail': 'blocks/%(blockHeight)s',
'outputs': 'outputs', 'outputs': 'outputs',
'statuses': 'statuses',
'transactions': 'transactions', 'transactions': 'transactions',
'transactionsDetail': 'transactions/%(transactionId)s', 'transactionsDetail': 'transactions/%(transactionId)s',
'assets': 'assets', 'assets': 'assets',
@ -59,30 +58,15 @@ test('Request with custom headers', t => {
test('Get block for a block id', t => { test('Get block for a block id', t => {
const expectedPath = 'path' const expectedPath = 'path'
const blockId = 'abc' const blockHeight = 'abc'
conn._req = sinon.spy() conn._req = sinon.spy()
conn.getApiUrls = sinon.stub().returns(expectedPath) conn.getApiUrls = sinon.stub().returns(expectedPath)
conn.getBlock(blockId) conn.getBlock(blockHeight)
t.truthy(conn._req.calledWith( t.truthy(conn._req.calledWith(
expectedPath, expectedPath,
{ urlTemplateSpec: { blockId } } { urlTemplateSpec: { blockHeight } }
))
})
test('Get status for a transaction id', t => {
const expectedPath = 'path'
const transactionId = 'abc'
conn._req = sinon.spy()
conn.getApiUrls = sinon.stub().returns(expectedPath)
conn.getStatus(transactionId)
t.truthy(conn._req.calledWith(
expectedPath,
{ query: { transaction_id: transactionId } }
)) ))
}) })
@ -105,18 +89,16 @@ test('Get transaction for a transaction id', t => {
test('Get list of blocks for a transaction id', t => { test('Get list of blocks for a transaction id', t => {
const expectedPath = 'path' const expectedPath = 'path'
const transactionId = 'abc' const transactionId = 'abc'
const status = 'status'
conn._req = sinon.spy() conn._req = sinon.spy()
conn.getApiUrls = sinon.stub().returns(expectedPath) conn.getApiUrls = sinon.stub().returns(expectedPath)
conn.listBlocks(transactionId, status) conn.listBlocks(transactionId)
t.truthy(conn._req.calledWith( t.truthy(conn._req.calledWith(
expectedPath, expectedPath,
{ {
query: { query: {
transaction_id: transactionId, transaction_id: transactionId,
status
} }
} }
)) ))

View File

@ -4,6 +4,7 @@ import { Transaction, Ed25519Keypair } from '../src'
// NOTE: It's safer to cast `Math.random()` to a string, to avoid differences // NOTE: It's safer to cast `Math.random()` to a string, to avoid differences
// in "float interpretation" between languages (e.g. JavaScript and Python) // 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 function asset() { return { message: `${Math.random()}` } }
export const metaData = { message: 'metaDataMessage' } export const metaData = { message: 'metaDataMessage' }

View File

@ -2,6 +2,7 @@ import test from 'ava'
import { Ed25519Keypair, Transaction, Connection } from '../../src' import { Ed25519Keypair, Transaction, Connection } from '../../src'
import { import {
API_PATH,
alice, alice,
aliceCondition, aliceCondition,
aliceOutput, aliceOutput,
@ -11,8 +12,6 @@ import {
metaData metaData
} from '../constants' } from '../constants'
const API_PATH = 'http://localhost:9984/api/v1/'
test('Keypair is created', t => { test('Keypair is created', t => {
const keyPair = new Ed25519Keypair() const keyPair = new Ed25519Keypair()
@ -36,8 +35,7 @@ test('Valid CREATE transaction', t => {
) )
const txSigned = Transaction.signTransaction(tx, alice.privateKey) const txSigned = Transaction.signTransaction(tx, alice.privateKey)
return conn.postTransaction(txSigned) return conn.postTransactionCommit(txSigned)
.then(({ id }) => conn.pollStatusAndFetchTransaction(id))
.then(resTx => t.truthy(resTx)) .then(resTx => t.truthy(resTx))
}) })
@ -55,8 +53,7 @@ test('Valid TRANSFER transaction with single Ed25519 input', t => {
alice.privateKey alice.privateKey
) )
return conn.postTransaction(createTxSigned) return conn.postTransactionCommit(createTxSigned)
.then(({ id }) => conn.pollStatusAndFetchTransaction(id))
.then(() => { .then(() => {
const transferTx = Transaction.makeTransferTransaction( const transferTx = Transaction.makeTransferTransaction(
[{ tx: createTxSigned, output_index: 0 }], [{ tx: createTxSigned, output_index: 0 }],
@ -67,8 +64,7 @@ test('Valid TRANSFER transaction with single Ed25519 input', t => {
transferTx, transferTx,
alice.privateKey alice.privateKey
) )
return conn.postTransaction(transferTxSigned) return conn.postTransactionCommit(transferTxSigned)
.then(({ id }) => conn.pollStatusAndFetchTransaction(id))
.then(resTx => t.truthy(resTx)) .then(resTx => t.truthy(resTx))
}) })
}) })
@ -87,8 +83,7 @@ test('Valid TRANSFER transaction with multiple Ed25519 inputs', t => {
alice.privateKey alice.privateKey
) )
return conn.postTransaction(createTxSigned) return conn.postTransactionCommit(createTxSigned)
.then(({ 'id': txId }) => conn.pollStatusAndFetchTransaction(txId))
.then(() => { .then(() => {
const transferTx = Transaction.makeTransferTransaction( const transferTx = Transaction.makeTransferTransaction(
[{ tx: createTxSigned, output_index: 0 }, { tx: createTxSigned, output_index: 1 }], [{ 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, alice.privateKey,
bob.privateKey bob.privateKey
) )
return conn.postTransaction(transferTxSigned) return conn.postTransactionCommit(transferTxSigned)
.then(({ id }) => conn.pollStatusAndFetchTransaction(id))
.then(resTx => t.truthy(resTx)) .then(resTx => t.truthy(resTx))
}) })
}) })
@ -129,8 +123,7 @@ test('Valid TRANSFER transaction with multiple Ed25519 inputs from different tra
alice.privateKey alice.privateKey
) )
return conn.postTransaction(createTxSigned) return conn.postTransactionCommit(createTxSigned)
.then(({ 'id': txId }) => conn.pollStatusAndFetchTransaction(txId))
.then(() => { .then(() => {
const transferTx1 = Transaction.makeTransferTransaction( const transferTx1 = Transaction.makeTransferTransaction(
[{ tx: createTxSigned, output_index: 0 }], [{ tx: createTxSigned, output_index: 0 }],
@ -151,10 +144,8 @@ test('Valid TRANSFER transaction with multiple Ed25519 inputs from different tra
bob.privateKey bob.privateKey
) )
return conn.postTransaction(transferTxSigned1) return conn.postTransactionCommit(transferTxSigned1)
.then(({ id }) => conn.pollStatusAndFetchTransaction(id)) .then(conn.postTransactionCommit(transferTxSigned2))
.then(conn.postTransaction(transferTxSigned2))
.then(({ id }) => conn.pollStatusAndFetchTransaction(id))
.then(() => { .then(() => {
const transferTxMultipleInputs = Transaction.makeTransferTransaction( const transferTxMultipleInputs = Transaction.makeTransferTransaction(
[{ tx: transferTxSigned1, output_index: 0 }, [{ tx: transferTxSigned1, output_index: 0 },
@ -167,14 +158,12 @@ test('Valid TRANSFER transaction with multiple Ed25519 inputs from different tra
carol.privateKey, carol.privateKey,
trent.privateKey trent.privateKey
) )
return conn.postTransaction(transferTxSignedMultipleInputs) return conn.postTransactionCommit(transferTxSignedMultipleInputs)
.then(({ id }) => conn.pollStatusAndFetchTransaction(id))
.then(resTx => t.truthy(resTx)) .then(resTx => t.truthy(resTx))
}) })
}) })
}) })
test('Search for spent and unspent outputs of a given public key', t => { test('Search for spent and unspent outputs of a given public key', t => {
const conn = new Connection(API_PATH) const conn = new Connection(API_PATH)
const carol = new Ed25519Keypair() const carol = new Ed25519Keypair()
@ -208,10 +197,8 @@ test('Search for spent and unspent outputs of a given public key', t => {
carol.privateKey, carol.privateKey,
) )
return conn.postTransaction(createTxSigned) return conn.postTransactionCommit(createTxSigned)
.then(({ id }) => conn.pollStatusAndFetchTransaction(id)) .then(() => conn.postTransactionCommit(transferTxSigned))
.then(() => conn.postTransaction(transferTxSigned))
.then(({ id }) => conn.pollStatusAndFetchTransaction(id))
.then(() => conn.listOutputs(carol.publicKey)) .then(() => conn.listOutputs(carol.publicKey))
// now listOutputs should return us outputs 0 and 1 (unfiltered) // now listOutputs should return us outputs 0 and 1 (unfiltered)
.then(outputs => t.truthy(outputs.length === 2)) .then(outputs => t.truthy(outputs.length === 2))
@ -250,10 +237,8 @@ test('Search for unspent outputs for a given public key', t => {
carol.privateKey, carol.privateKey,
) )
return conn.postTransaction(createTxSigned) return conn.postTransactionCommit(createTxSigned)
.then(({ id }) => conn.pollStatusAndFetchTransaction(id)) .then(() => conn.postTransactionCommit(transferTxSigned))
.then(() => conn.postTransaction(transferTxSigned))
.then(({ id }) => conn.pollStatusAndFetchTransaction(id))
// now listOutputs should return us outputs 0 and 2 (1 is spent) // now listOutputs should return us outputs 0 and 2 (1 is spent)
.then(() => conn.listOutputs(carol.publicKey, 'false')) .then(() => conn.listOutputs(carol.publicKey, 'false'))
.then(outputs => t.truthy(outputs.length === 2)) .then(outputs => t.truthy(outputs.length === 2))
@ -292,10 +277,8 @@ test('Search for spent outputs for a given public key', t => {
carol.privateKey, carol.privateKey,
) )
return conn.postTransaction(createTxSigned) return conn.postTransactionCommit(createTxSigned)
.then(({ id }) => conn.pollStatusAndFetchTransaction(id)) .then(() => conn.postTransactionCommit(transferTxSigned))
.then(() => conn.postTransaction(transferTxSigned))
.then(({ id }) => conn.pollStatusAndFetchTransaction(id))
// now listOutputs should only return us output 1 (0 and 2 are unspent) // now listOutputs should only return us output 1 (0 and 2 are unspent)
.then(() => conn.listOutputs(carol.publicKey, true)) .then(() => conn.listOutputs(carol.publicKey, true))
.then(outputs => t.truthy(outputs.length === 1)) .then(outputs => t.truthy(outputs.length === 1))
@ -316,8 +299,7 @@ test('Search for an asset', t => {
alice.privateKey alice.privateKey
) )
return conn.postTransaction(createTxSigned) return conn.postTransactionCommit(createTxSigned)
.then(({ id }) => conn.pollStatusAndFetchTransaction(id))
.then(() => conn.searchAssets(createTxSigned.asset.data.message)) .then(() => conn.searchAssets(createTxSigned.asset.data.message))
.then(assets => t.truthy( .then(assets => t.truthy(
assets.pop(), assets.pop(),
@ -340,8 +322,7 @@ test('Search for metadata', t => {
alice.privateKey alice.privateKey
) )
return conn.postTransaction(createTxSigned) return conn.postTransactionCommit(createTxSigned)
.then(({ id }) => conn.pollStatusAndFetchTransaction(id))
.then(() => conn.searchMetadata(createTxSigned.metadata.message)) .then(() => conn.searchMetadata(createTxSigned.metadata.message))
.then(assets => t.truthy( .then(assets => t.truthy(
assets.pop(), assets.pop(),
@ -349,6 +330,7 @@ test('Search for metadata', t => {
)) ))
}) })
test('Search blocks containing a transaction', t => { test('Search blocks containing a transaction', t => {
const conn = new Connection(API_PATH) const conn = new Connection(API_PATH)
@ -363,11 +345,10 @@ test('Search blocks containing a transaction', t => {
alice.privateKey alice.privateKey
) )
return conn.postTransaction(createTxSigned) return conn.postTransactionCommit(createTxSigned)
.then(({ id }) => conn.pollStatusAndFetchTransaction(id)) .then(({ id }) => conn.listBlocks(id))
.then(({ id }) => conn.listBlocks(id, 'VALID')) .then(blockHeight => conn.getBlock(blockHeight.pop()))
.then(blocks => conn.getBlock(blocks.pop())) .then(({ transactions }) => transactions.filter(({ id }) => id === createTxSigned.id))
.then(({ block: { transactions } }) => transactions.filter(({ id }) => id === createTxSigned.id))
.then(transactions => t.truthy(transactions.length === 1)) .then(transactions => t.truthy(transactions.length === 1))
}) })
@ -386,10 +367,11 @@ test('Search transaction containing an asset', t => {
alice.privateKey alice.privateKey
) )
return conn.postTransaction(createTxSigned) return conn.postTransactionCommit(createTxSigned)
.then(({ id }) => conn.pollStatusAndFetchTransaction(id, 'CREATE'))
.then(({ id }) => conn.listTransactions(id)) .then(({ id }) => conn.listTransactions(id))
.then(transactions => t.truthy(transactions.length === 1)) .then(transactions => {
t.truthy(transactions.length === 1)
})
}) })

View File

@ -1,6 +1,7 @@
import test from 'ava' import test from 'ava'
import cc from 'crypto-conditions' import cc from 'crypto-conditions'
import { Ed25519Keypair, Transaction, ccJsonLoad } from '../../src' import { Ed25519Keypair, Transaction, ccJsonLoad } from '../../src'
import sha256Hash from '../../src/sha256Hash'
test('Ed25519 condition encoding', t => { test('Ed25519 condition encoding', t => {
const publicKey = '4zvwRjXUKGfvwnParsHAS3HuSVzV5cA4McphgmoCtajS' const publicKey = '4zvwRjXUKGfvwnParsHAS3HuSVzV5cA4McphgmoCtajS'
@ -53,16 +54,27 @@ test('Fulfillment correctly formed', t => {
[Transaction.makeOutput(Transaction.makeEd25519Condition(alice.publicKey))], [Transaction.makeOutput(Transaction.makeEd25519Condition(alice.publicKey))],
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( const txTransfer = Transaction.makeTransferTransaction(
[{ tx: txCreate, output_index: 0 }], [{ tx: signCreateTransaction, output_index: 0 }],
[Transaction.makeOutput(Transaction.makeEd25519Condition(alice.publicKey))], [Transaction.makeOutput(Transaction.makeEd25519Condition(alice.publicKey))],
{} {}
) )
const msg = Transaction.serializeTransactionIntoCanonicalString(txTransfer)
const txSigned = Transaction.signTransaction(txTransfer, alice.privateKey) 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( t.truthy(cc.validateFulfillment(
txSigned.inputs[0].fulfillment, txCreate.outputs[0].condition.uri, txSigned.inputs[0].fulfillment, txCreate.outputs[0].condition.uri,
Buffer.from(msg) Buffer.from(msgHash, 'hex')
)) ))
}) })

View File

@ -54,6 +54,7 @@ const PLUGINS = [
] ]
const PROD_PLUGINS = [ const PROD_PLUGINS = [
/*
new webpack.optimize.UglifyJsPlugin({ new webpack.optimize.UglifyJsPlugin({
compress: { compress: {
warnings: false, warnings: false,
@ -67,6 +68,7 @@ const PROD_PLUGINS = [
debug: false, debug: false,
minimize: true, minimize: true,
}), }),
*/
] ]
if (PRODUCTION) { if (PRODUCTION) {