diff --git a/.gitignore b/.gitignore index 0412520..9911ee5 100644 --- a/.gitignore +++ b/.gitignore @@ -19,5 +19,6 @@ coverage coverage.lcov .nyc_output yarn.lock - +docs/build/ +docs/_build/ docs/build/ diff --git a/README.md b/README.md index 9b6efb9..285ee2f 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,11 @@ | BigchainDB Server | BigchainDB JavaScript Driver | | ----------------- |------------------------------| | `0.10` | `0.1.x` | -| `>= 1.0.0` | `0.3.x` | +| `1.0.0` | `0.3.x` | +| `>= 1.3.0` | `3.x.x` | +## Breaking changes +Version 3.2 of BigchainDB JavaScript Driver introduces a new way of creating transfer transactions. Check [older versions](https://docs.bigchaindb.com/projects/js-driver/en/latest/readme.html#features) ## Contents diff --git a/docs/source/.conf.py.swp b/docs/source/.conf.py.swp new file mode 100644 index 0000000..24d0812 Binary files /dev/null and b/docs/source/.conf.py.swp differ diff --git a/docs/source/advanced.rst b/docs/source/advanced.rst index 3c9c661..3f9d2c3 100644 --- a/docs/source/advanced.rst +++ b/docs/source/advanced.rst @@ -14,7 +14,7 @@ First, we create an asset registering the bicycle: const txCreateAliceSimple = driver.Transaction.makeCreateTransaction( {'asset': 'bicycle'}, {'purchase_price': '€240'}, - [ + [ driver.Transaction.makeOutput(driver.Transaction.makeEd25519Condition(alice.publicKey)) ], alice.publicKey @@ -29,10 +29,10 @@ So, Alice needs a crypto conditions that defines that she or her daughter can si We need to define a threshold as well. This defines how many persons have to sign the transaction to ``TRANSFER`` it. In this case, we define two subconditions with the public keys from Alice and Carly. Next, we set the threshold to **one**. This means that just one of the subconditions has to sign the transaction to transfer it. -This can be the mother Alice, or Carly herself. +This can be the mother Alice, or Carly herself. .. code-block:: js - + // Create condition for Alice and Carly let subConditionFrom = driver.Transaction.makeEd25519Condition(alice.publicKey, false) let subConditionTo = driver.Transaction.makeEd25519Condition(carly.publicKey, false) @@ -47,11 +47,10 @@ This can be the mother Alice, or Carly herself. output.public_keys = [carly.publicKey] let transaction = driver.Transaction.makeTransferTransaction( - txCreateAliceSimpleSigned, - {'meta': 'Transfer to new user with conditions'}, - [output], - 0 - ); + [{ tx: txCreateAliceSimpleSigned, output_index: 0 }], + [output], + {'meta': 'Transfer to new user with conditions'} + ); // Add alice as previous owner transaction.inputs[0].owners_before = [alice.publicKey] diff --git a/docs/source/index.rst b/docs/source/index.rst index cc3f2d9..001a047 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -3,7 +3,7 @@ BigchainDB Javascript Driver Documentation .. toctree:: :maxdepth: 2 - + ← Back to All BigchainDB Docs readme quickstart diff --git a/docs/source/readme.rst b/docs/source/readme.rst index 36d4f5c..afccbd1 100644 --- a/docs/source/readme.rst +++ b/docs/source/readme.rst @@ -34,3 +34,49 @@ Compatibility Matrix +-----------------------+----------------------------------+ | ``1.0`` | ``0.3.x`` | +-----------------------+----------------------------------+ +| ``1.3`` | ``3.x.x`` | ++-----------------------+----------------------------------+ + + +Older versions +-------------------- +For versions below 3.2, a transfer transaction looked like: + +.. code-block:: js + + const createTranfer = BigchainDB.Transaction.makeTransferTransaction( + txCreated, + metadata, [BigchainDB.Transaction.makeOutput( + BigchainDB.Transaction.makeEd25519Condition(alice.publicKey))], + 0 + ) + + const signedTransfer = BigchainDB.Transaction.signTransaction(createTranfer, keypair.privateKey) + + +In order to upgrade and do it compatible with the new driver version, this transaction should be now: + +.. code-block:: js + + const createTranfer = BigchainDB.Transaction.makeTransferTransaction( + [{ tx: txCreated, output_index: 0 }], + [aliceOutput], + metaData + ) + + const signedTransfer = BigchainDB.Transaction.signTransaction(createTranfer, keypair.privateKey) + + +The upgrade allows to create transfer transaction spending outputs that belong to different transactions. +So for instance is now possible to create a transfer transaction spending two outputs from two different create transactions: + + +.. code-block:: js + + const createTranfer = BigchainDB.Transaction.makeTransferTransaction( + [{ tx: txCreated1, output_index: 0 }, { tx: txCreated2, output_index: 0 }], + [aliceOutput], + metaData + ) + + const signedTransfer = BigchainDB.Transaction.signTransaction(createTranfer, keypair.privateKey) diff --git a/docs/source/usage.rst b/docs/source/usage.rst index 268f0b3..eebc5fe 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -173,22 +173,20 @@ First, let's prepare the transaction to be transferred. .. code-block:: js const txTransferBob = driver.Transaction.makeTransferTransaction( - // signedTx to transfer - txCreateAliceSimpleSigned, - - // metadata - {price: '100 euro'}, + // signedTx to transfer and output index + [{ tx: txCreateAliceSimpleSigned, output_index: 0 }], [driver.Transaction.makeOutput(driver.Transaction.makeEd25519Condition(bob.publicKey))], - 0 + + // metadata + {price: '100 euro'} ); The function ``makeTransferTransaction()`` needs following parameters: -- Unspent transaction: Previous transaction you have control over (i.e. can fulfill its Output Condition) -- Metadata for transaction (e.g. price of sold bike) +- Unspent outputs: Array of `unspent transactions outputs`. Each item contains `Transaction` itself and index of `unspent output` for that `Transaction`. - Array of output objects to add to the transaction: Think of these as the recipients of the asset after the transaction. For `TRANSFER` transactions, this should usually just be a list of outputs wrapping Ed25519 conditions generated from the public keys of the recipients. -- Indices of the outputs in `unspent transaction` that this transaction fulfills. +- Metadata for transaction (e.g. price of sold bike) Fulfill transaction by signing it with Alice's private key. @@ -377,12 +375,12 @@ Recap: Asset Creation & Transfer // Transfer bicycle to Bob .then(() => { const txTransferBob = driver.Transaction.makeTransferTransaction( - // signedTx to transfer - txCreateAliceSimpleSigned, - // metadata - {price: '100 euro'}, + // signedTx to transfer and output index + [{ tx: txCreateAliceSimpleSigned, output_index: 0 }], [driver.Transaction.makeOutput(driver.Transaction.makeEd25519Condition(bob.publicKey))], - 0) + // metadata + {price: '100 euro'} + ) // Sign with alice's private key let txTransferBobSigned = driver.Transaction.signTransaction(txTransferBob, alice.privateKey) @@ -617,10 +615,10 @@ and further we transfer it from Bob to Chris. Expectations: // Transfer bicycle from Alice to Bob .then(() => { const txTransferBob = driver.Transaction.makeTransferTransaction( - txCreateAliceSimpleSigned, - {'newOwner': 'Bob'}, + [{ tx: txCreateAliceSimpleSigned, output_index: 0 }], [driver.Transaction.makeOutput(driver.Transaction.makeEd25519Condition(bob.publicKey))], - 0) + {'newOwner': 'Bob'} + ) // Sign with alice's private key txTransferBobSigned = driver.Transaction.signTransaction(txTransferBob, alice.privateKey) @@ -634,10 +632,10 @@ and further we transfer it from Bob to Chris. Expectations: // Second transfer of bicycle from Bob to Chris .then(tx => { const txTransferChris = driver.Transaction.makeTransferTransaction( - txTransferBobSigned, - {'newOwner': 'Chris'}, + [{ tx: txTransferBobSigned, output_index: 0 }], [driver.Transaction.makeOutput(driver.Transaction.makeEd25519Condition(chris.publicKey))], - 0) + {'newOwner': 'Chris'} + ) // Sign with bob's private key let txTransferChrisSigned = driver.Transaction.signTransaction(txTransferChris, bob.privateKey) @@ -722,15 +720,16 @@ This gives us 4 tokens to transfer. .. code-block:: js const txTransferDivisible = driver.Transaction.makeTransferTransaction( - txCreateAliceDivisibleSigned, - { - metaDataMessage: 'I am specific to this transfer transaction' - }, + [{ tx: txCreateAliceDivisibleSigned, output_index: 0 }], [ driver.Transaction.makeOutput(driver.Transaction.makeEd25519Condition(carly.publicKey), '2'), driver.Transaction.makeOutput(driver.Transaction.makeEd25519Condition(bob.publicKey), '1'), driver.Transaction.makeOutput(driver.Transaction.makeEd25519Condition(alice.publicKey), '1') - ], 0); + ], + { + metaDataMessage: 'I am specific to this transfer transaction' + } + ); To make the use of the last parameter of ``makeTransferTransaction()`` function more clear, we will do another transfer. We will fulfill the first and second output of the create transaction (0, 1) because Carly and Bob decide to redistribute some money. @@ -741,16 +740,16 @@ We will fulfill the first and second output of the create transaction (0, 1) bec This gives us 3 tokens to redistribute. I want to give 1 token to Carly and 2 tokens Alice. .. code-block:: js - const txTransferDivisibleInputs = driver.Transaction.makeTransferTransaction( - txTransferDivisibleSigned, - { - metaDataMessage: 'I am specific to this transfer transaction' - }, + [{ tx: txTransferDivisibleSigned, output_index: 0 }, { tx: txTransferDivisibleSigned, output_index: 1 }], [ driver.Transaction.makeOutput(driver.Transaction.makeEd25519Condition(carly.publicKey), '1'), driver.Transaction.makeOutput(driver.Transaction.makeEd25519Condition(alice.publicKey), '2') - ], 0, 1) + ], + { + metaDataMessage: 'I am specific to this transfer transaction' + } + ); Because we want to fulfill two outputs (Carly and Bob), we have to sign the transfer transaction in the same order: diff --git a/src/transaction/makeTransferTransaction.js b/src/transaction/makeTransferTransaction.js index 3d62bd2..b2a5110 100644 --- a/src/transaction/makeTransferTransaction.js +++ b/src/transaction/makeTransferTransaction.js @@ -5,45 +5,42 @@ import makeTransaction from './makeTransaction' /** * @public * Generate a `TRANSFER` transaction holding the `asset`, `metadata`, and `outputs`, that fulfills - * the `fulfilledOutputs` of `unspentTransaction`. - * @param {object} unspentTransaction Previous Transaction you have control over (i.e. can fulfill - * its Output Condition) - * @param {object} metadata Metadata for the Transaction + * the `fulfilledOutputs` of each `unspentTransaction`. + * @param {object[]} unspentOutputs Array of unspent Transactions' Outputs. + * Each item contains Transaction itself + * and index of unspent Output for that Transaction. * @param {object[]} outputs Array of Output objects to add to the Transaction. * Think of these as the recipients of the asset after the transaction. * For `TRANSFER` Transactions, this should usually just be a list of * Outputs wrapping Ed25519 Conditions generated from the public keys of * the recipients. - * @param {...number} OutputIndices Indices of the Outputs in `unspentTransaction` that this - * Transaction fulfills. - * Note that listed public keys listed must be used (and in - * the same order) to sign the Transaction - * (`signTransaction()`). + * @param {object} metadata Metadata for the Transaction - optional * @returns {object} Unsigned transaction -- make sure to call signTransaction() on it before * sending it off! */ -// TODO: -// - Make `metadata` optional argument export default function makeTransferTransaction( - unspentTransaction, - metadata, + unspentOutputs, outputs, - ...outputIndices + metadata ) { - const inputs = outputIndices.map((outputIndex) => { - const fulfilledOutput = unspentTransaction.outputs[outputIndex] + const inputs = unspentOutputs.map((unspentOutput) => { + const tx = unspentOutput.tx + const outputIndex = unspentOutput.output_index + const fulfilledOutput = tx.outputs[outputIndex] const transactionLink = { 'output_index': outputIndex, - 'transaction_id': unspentTransaction.id, + 'transaction_id': tx.id, } return makeInputTemplate(fulfilledOutput.public_keys, transactionLink) }) const assetLink = { - 'id': unspentTransaction.operation === 'CREATE' ? unspentTransaction.id - : unspentTransaction.asset.id + 'id': unspentOutputs[0].tx.operation === 'CREATE' ? unspentOutputs[0].tx.id + : unspentOutputs[0].tx.asset.id } - return makeTransaction('TRANSFER', assetLink, metadata, outputs, inputs) + const meta = metadata || null + + return makeTransaction('TRANSFER', assetLink, meta, outputs, inputs) } diff --git a/test/constants.js b/test/constants.js index cbfa4c0..bcd9b5d 100644 --- a/test/constants.js +++ b/test/constants.js @@ -17,10 +17,9 @@ export const createTx = Transaction.makeCreateTransaction( alice.publicKey ) export const transferTx = Transaction.makeTransferTransaction( - createTx, - metaData, + [{ tx: createTx, output_index: 0 }], [aliceOutput], - 0 + metaData ) export const bob = new Ed25519Keypair() diff --git a/test/integration/test_integration.js b/test/integration/test_integration.js index 09e261f..8f7a53b 100644 --- a/test/integration/test_integration.js +++ b/test/integration/test_integration.js @@ -59,10 +59,9 @@ test('Valid TRANSFER transaction with single Ed25519 input', t => { .then(({ id }) => conn.pollStatusAndFetchTransaction(id)) .then(() => { const transferTx = Transaction.makeTransferTransaction( - createTxSigned, - metaData, + [{ tx: createTxSigned, output_index: 0 }], [aliceOutput], - 0 + metaData ) const transferTxSigned = Transaction.signTransaction( transferTx, @@ -92,11 +91,9 @@ test('Valid TRANSFER transaction with multiple Ed25519 inputs', t => { .then(({ 'id': txId }) => conn.pollStatusAndFetchTransaction(txId)) .then(() => { const transferTx = Transaction.makeTransferTransaction( - createTxSigned, - metaData, + [{ tx: createTxSigned, output_index: 0 }, { tx: createTxSigned, output_index: 1 }], [Transaction.makeOutput(aliceCondition, '2')], - 0, - 1 + metaData ) const transferTxSigned = Transaction.signTransaction( transferTx, @@ -110,6 +107,74 @@ test('Valid TRANSFER transaction with multiple Ed25519 inputs', t => { }) +test('Valid TRANSFER transaction with multiple Ed25519 inputs from different transactions', t => { + const conn = new Connection(API_PATH) + const carol = new Ed25519Keypair() + const carolCondition = Transaction.makeEd25519Condition(carol.publicKey) + const carolOutput = Transaction.makeOutput(carolCondition) + const trent = new Ed25519Keypair() + const trentCondition = Transaction.makeEd25519Condition(trent.publicKey) + const trentOutput = Transaction.makeOutput(trentCondition) + const eli = new Ed25519Keypair() + const eliCondition = Transaction.makeEd25519Condition(eli.publicKey) + + const createTx = Transaction.makeCreateTransaction( + asset(), + metaData, + [aliceOutput, bobOutput], + alice.publicKey + ) + const createTxSigned = Transaction.signTransaction( + createTx, + alice.privateKey + ) + + return conn.postTransaction(createTxSigned) + .then(({ 'id': txId }) => conn.pollStatusAndFetchTransaction(txId)) + .then(() => { + const transferTx1 = Transaction.makeTransferTransaction( + [{ tx: createTxSigned, output_index: 0 }], + [carolOutput], + metaData + ) + const transferTxSigned1 = Transaction.signTransaction( + transferTx1, + alice.privateKey + ) + const transferTx2 = Transaction.makeTransferTransaction( + [{ tx: createTxSigned, output_index: 1 }], + [trentOutput], + metaData + ) + const transferTxSigned2 = Transaction.signTransaction( + transferTx2, + bob.privateKey + ) + + return conn.postTransaction(transferTxSigned1) + .then(({ id }) => conn.pollStatusAndFetchTransaction(id)) + .then(conn.postTransaction(transferTxSigned2)) + .then(({ id }) => conn.pollStatusAndFetchTransaction(id)) + .then(() => { + const transferTxMultipleInputs = Transaction.makeTransferTransaction( + [{ tx: transferTxSigned1, output_index: 0 }, + { tx: transferTxSigned2, output_index: 0 }], + [Transaction.makeOutput(eliCondition, '2')], + metaData + ) + const transferTxSignedMultipleInputs = Transaction.signTransaction( + transferTxMultipleInputs, + carol.privateKey, + trent.privateKey + ) + return conn.postTransaction(transferTxSignedMultipleInputs) + .then(({ id }) => conn.pollStatusAndFetchTransaction(id)) + .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() @@ -134,10 +199,9 @@ test('Search for spent and unspent outputs of a given public key', t => { // We spent output 1 (of 0, 1) const transferTx = Transaction.makeTransferTransaction( - createTxSigned, - metaData, + [{ tx: createTxSigned, output_index: 1 }], [trentOutput], - 1 + metaData ) const transferTxSigned = Transaction.signTransaction( transferTx, @@ -177,10 +241,9 @@ test('Search for unspent outputs for a given public key', t => { // We spent output 1 (of 0, 1, 2) const transferTx = Transaction.makeTransferTransaction( - createTxSigned, - metaData, + [{ tx: createTxSigned, output_index: 1 }], [trentOutput], - 1 + metaData ) const transferTxSigned = Transaction.signTransaction( transferTx, @@ -220,10 +283,9 @@ test('Search for spent outputs for a given public key', t => { // We spent output 1 (of 0, 1, 2) const transferTx = Transaction.makeTransferTransaction( - createTxSigned, - metaData, + [{ tx: createTxSigned, output_index: 1 }], [trentOutput], - 1 + metaData ) const transferTxSigned = Transaction.signTransaction( transferTx, diff --git a/test/transaction/test_cryptoconditions.js b/test/transaction/test_cryptoconditions.js index 0b5f775..5af5256 100644 --- a/test/transaction/test_cryptoconditions.js +++ b/test/transaction/test_cryptoconditions.js @@ -56,10 +56,9 @@ test('Fulfillment correctly formed', t => { alice.publicKey ) const txTransfer = Transaction.makeTransferTransaction( - txCreate, - {}, + [{ tx: txCreate, output_index: 0 }], [Transaction.makeOutput(Transaction.makeEd25519Condition(alice.publicKey))], - [0] + {} ) const msg = Transaction.serializeTransactionIntoCanonicalString(txTransfer) const txSigned = Transaction.signTransaction(txTransfer, alice.privateKey) @@ -69,6 +68,22 @@ test('Fulfillment correctly formed', t => { }) +test('CryptoConditions JSON load', t => { + const cond = Transaction.ccJsonLoad({ + type: 'threshold-sha-256', + threshold: 1, + subconditions: [{ + type: 'ed25519-sha-256', + public_key: 'a' + }, + { + hash: 'a' + }], + }) + t.truthy(cond.subconditions.length === 2) +}) + + test('CryptoConditions JSON load', t => { const cond = Transaction.ccJsonLoad({ type: 'threshold-sha-256', diff --git a/test/transaction/test_transaction.js b/test/transaction/test_transaction.js index d5a28ed..002c82e 100644 --- a/test/transaction/test_transaction.js +++ b/test/transaction/test_transaction.js @@ -73,10 +73,9 @@ test('Create TRANSFER transaction based on CREATE transaction', t => { sinon.spy(makeTransaction, 'default') Transaction.makeTransferTransaction( - createTx, - metaData, + [{ tx: createTx, output_index: 0 }], [aliceOutput], - 0 + metaData ) const expected = [ 'TRANSFER', @@ -101,10 +100,9 @@ test('Create TRANSFER transaction based on TRANSFER transaction', t => { sinon.spy(makeTransaction, 'default') Transaction.makeTransferTransaction( - transferTx, - metaData, + [{ tx: transferTx, output_index: 0 }], [aliceOutput], - 0 + metaData ) const expected = [ 'TRANSFER',