Merge pull request #125 from bigchaindb/transfer-multiple-inputs-rebase

Transfer multiple inputs rebase
This commit is contained in:
Manolo 2017-12-20 12:53:04 +01:00 committed by GitHub
commit 879ae68d0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 209 additions and 90 deletions

3
.gitignore vendored
View File

@ -19,5 +19,6 @@ coverage
coverage.lcov
.nyc_output
yarn.lock
docs/build/
docs/_build/
docs/build/

View File

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

BIN
docs/source/.conf.py.swp Normal file

Binary file not shown.

View File

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

View File

@ -3,7 +3,7 @@ BigchainDB Javascript Driver Documentation
.. toctree::
:maxdepth: 2
← Back to All BigchainDB Docs <https://bigchaindb.readthedocs.io/en/latest/index.html>
readme
quickstart

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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