mirror of
https://github.com/bigchaindb/js-bigchaindb-driver.git
synced 2024-11-22 01:36:56 +01:00
Merge pull request #125 from bigchaindb/transfer-multiple-inputs-rebase
Transfer multiple inputs rebase
This commit is contained in:
commit
879ae68d0c
3
.gitignore
vendored
3
.gitignore
vendored
@ -19,5 +19,6 @@ coverage
|
||||
coverage.lcov
|
||||
.nyc_output
|
||||
yarn.lock
|
||||
|
||||
docs/build/
|
||||
docs/_build/
|
||||
docs/build/
|
||||
|
@ -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
BIN
docs/source/.conf.py.swp
Normal file
Binary file not shown.
@ -47,10 +47,9 @@ 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'},
|
||||
[{ tx: txCreateAliceSimpleSigned, output_index: 0 }],
|
||||
[output],
|
||||
0
|
||||
{'meta': 'Transfer to new user with conditions'}
|
||||
);
|
||||
|
||||
// Add alice as previous owner
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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,
|
||||
|
@ -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',
|
||||
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user