Difference between revisions of "Transaction Construction Algorithm (voting pools)"

From Open Transactions
Jump to navigation Jump to search
(Output List Initialization)
(One intermediate revision by the same user not shown)
Line 1: Line 1:
#REDIRECT [[:Category:Transaction Construction Algorithm (voting pools)]]
In order to avoid an expensive and error-prone consensus process, it is mandatory for wallets to be able to construct withdrawal transactions in a deterministic manner, assuming they are given the withdrawal requests in a deterministic order. Once this is accomplished, then it only becomes necessary for wallets to share signatures among themselves because they know they all signed the exact same bitcoin transaction.
<div style="float: right"><include iframe src="https://www.lucidchart.com/documents/embeddedchart/eb960c32-71d4-4cfc-8f49-63a82b343608/0" width="300" height="1004" frameborder="0" scrolling="yes" /></div>
====Initial Conditions====
The [[startwithdrawal]] API call has been received by the wallet, and all the arguments have been checked for errors.
The basic flow of the procedure is:
# Gather accounting information from the API call arguments
# Validate and sort the output list
# Start a new transactions
# Add outputs to the transaction until either all outputs are added or all inputs are used.
# Make sure all generated transactions are ready for signing.
# Create a list of all signatures which the wallet is capable of generating.
# Create a status list containing the deposition of every output the caller specified.
# Return the status list and signature list to the caller.
Each step in this chart is more fully described below.
<div style="float: right"><include iframe src="https://www.lucidchart.com/documents/embeddedchart/0dd5f7d6-8511-4440-a535-32ad8508d146" width="150" height="502" frameborder="0" scrolling="yes" /></div>
====Initial Conditions====
The [[startwithdrawal]] API call has been received by the wallet, and all the arguments have been checked for errors.
# The algorithm must keep track of the next change address to be used. The initial value is supplied as the <code>[[Startwithdrawal#Arguments|changestart]]</code> argument. Any time a change output is allocated, the index value of the [[address identifier]] is incremented, and if a change output is remove from a transaction the index value is decremented. The final value will be returned to the caller as the <code>[[Withdrawal status|nextchangestart]]</code> value in the [[Withdrawal status| withdrawal status list]].
# Prepare an empty list for holding transactions as they are constructed and before they are signed.
# Prepare an empty array to hold the transaction signatures which will be returned to the caller as the <code>[[siglist| signatures]]</code> value.
# Prepare an <code>withdrawal status</code> object.
## <code>roundID</code>: copied from <code>roundID</code> argument
## <code>nextinputstart</code>: nil
## <code>nextchangestart</code>: nil
## <code>fees</code>: 0
## <code>outputs</code>: should contain an entry for every output passed:
### <code>outBailmentID</code>: copied from <code>outBailmentID</code> entry in <code>outputs</code> argument
### <code>status</code>: empty string
### <code>transactions</code>: nil
===Output List Initialization===
<div style="float: right"><include iframe src="https://www.lucidchart.com/documents/embeddedchart/56b10d7d-bbd4-4590-8200-c837c5ad953c" width="450" height="800" frameborder="0" scrolling="yes" /></div>
====Initial Conditions====
The [[startwithdrawal]] API call has been received by the wallet, and all the arguments have been checked for errors.
# Pass the <code>inputstart</code>, <code>inputstop</code>, and <code>dustthreshold</code> parameters to the [[Input Selection Algorithm (voting pools)|input selection algorithm]] to obtain an ordered list of eligible inputs.
# Perform a first pass check to determine if the requested outputs exceed the size of the eligible inputs. Note that this check is only definitive in the case where the output sum is greater than the input sum. At this stage of the algorithm, the input sum exceeding the output sum does not prove that all outputs will be successfully created.
## Take the sum of both lists.
## If the sum of the outputs exceeds the sum of the inputs, remove outputs in descending size order until this is no longer true. Note this may result in the output list being an empty set.
# If the output list is non-empty, sort it by <code>SHA256(server id, transaction number)</code> where the server id and transaction number come from the outBailmentID.
## Express the server id and transaction number as strings, and concatenate them prior to hashing
# Move both the input lists and outputs lists into separate stacks by pushing them in reverse list order. The first input and first output should be on the top of their respective stacks.
===Initialize New Transaction===
<div style="float: right"><include iframe src="https://www.lucidchart.com/documents/embeddedchart/506ed019-070c-44d6-b5e4-994169ab0704" width="150" height="450" frameborder="0" scrolling="yes" /></div>
====Initial Conditions====
* There are no pending transactions in the process of being constructed, either because this is the first transaction of the algorithm or because a previous pending transaction has been moved to the finished transaction list.
# Create a minimal transaction skeleton with a header and one change output.
## The change output will be the last output of the transaction, but the number of other outputs in this transaction is not yet known.
## The change output must be included in the skeleton for transaction size calculations.
## The position index for the change output is nil.
## The first non-change output for the transaction will be placed at position index 0.
After creating the transaction skeleton, and after any action which alters it, the new transaction size (bytes) and minimum required transaction fee must be recalculated.
===Add Next Output===
<div style="float: right"><include iframe src="https://www.lucidchart.com/documents/embeddedchart/3e9b2758-338e-40f7-8fe2-e036fda68157" width="600" height="800" frameborder="0" scrolling="yes" /></div>
====Initial Conditions====
The output stack contains at least one output.
There is a transaction in progress which is ready to accept new outputs. This transaction is either empty or it contains 1 or more outputs.
If the transaction already contains outputs, then it also contains sufficient inputs to cover those outputs and any required transaction fees without exceeding size limits. This is because it must have already successfully passed through this procedure before.
# Locate the entry for this output in the [[Withdrawal status|withdrawal status]] object by finding the corresponding outBailmentID.
# Validate the output address.
## If it is invalid, update <code>status</code> entry for this output to "invalid" and exit from this procedure.
## If it is valid, add it to the transaction.
### Add a new entry to the <code>transaction</code> array for this output:
###* <code>ntxid</code>: nil
###* <code>index</code>: next unused
###* <code>amount</code>: copied from [[Outputs (Voting Pool Wallet API)|outputs]] list.
# Check that the transaction does not exceed size limits. If it does, follow the [[Transaction Construction Algorithm (voting pools)#Oversize Transaction|Oversize Transaction]] procedure.
# Compare the sum of the inputs to the sum of the outputs and required transaction fee to determine if more inputs are required.
## If no more inputs are required, then update the <code>status</code> for this output to "success".
# If more inputs are required, then check to see if any more are available.
## According to the initial conditions, the input stack can not be empty on the first run through this loop.
## If the input stack is empty at this point, then the output has added at least one input beyond what was needed to satisfy the prior output (if there was a prior output)
## If the input stack is empty, then the output can only be partially fulfilled. Follow the [[Transaction Construction Algorithm (voting pools)#Split Output|Split Output]] procedure.
# Add the next input from the stack, and continue the loop at step 2.
The loop will continue until either:
* The transaction contains sufficient input value to satisfy the output plus transaction fees
* The transaction exceeds size limits
* There is not enough input value, but no more inputs are available
===Finalize Transaction===
<div style="float: right"><include iframe src="https://www.lucidchart.com/documents/embeddedchart/79c726f4-9d4e-4a3c-bd20-6556099d24ab" width="300" height="1004" frameborder="0" scrolling="yes" /></div>
====Initial Conditions====
* An unfinished transaction is ready for processing.
** The transaction may or may not have have a useful (non-change) output
** The transaction is valid
*** Not too big
*** Inputs are sufficient to cover outputs and required fees
** A change output may exist in the transaction
*** If a change output exists, it has a nil position and a nil value.
# If the transaction only contains a change output and no inputs, then it may be discarded.
# The change amount is calculated by: Σ(txin) - ( Σ(txout) + fees )
## If the change amount is greater than zero:
### Set the value of the output
### Set the position of the output to the next unused value after the last non-change output.
## If the change amount is zero, then the change output is removed from the transaction.
# Calculate an [[ntxid]] for the transaction for tracking purposes.
# Loop through every (non-change) output in the transaction and update the [[withdrawal status]] object for the corresponding output.
## The withdrawal status object contain an entry for every output in the transaction, where each entry has an <code>transactions</code> object containing a nil <code>ntxid</code>, an <code>index</code> matching the output position index, and a matching <code>amount</code>.
## Update the <code>ntxid</code> value from nil to the ntxid for the transaction.
# Add the fees from this transaction to the <code>fees</code> property of the withdrawal status object.
# Move the transaction to the finished transaction list.
===Update Signatures===
<div style="float: right"><include iframe src="https://www.lucidchart.com/documents/embeddedchart/97025b62-3720-4810-ae85-5ae678031aed" width="600" height="1100" frameborder="0" scrolling="yes" /></div>
====Initial Conditions====
* The finished transaction list contains zero or more finalized transactions
* The transactions are valid in all respects except for missing signatures
* If the finished transaction list is empty, the rest of the procedure can be skipped.
* Loop through each transaction.
** Within each transaction, loop through every input.
*** Within each input, loop through every pubkey in the [[redeem script]].
**** If the wallet possesses the private key corresponding to the pubkey, create the appropriate signature.
***** Add the signature to the transaction.
***** Add the signature to the [[siglist|signature list]] in the appropriate position.
* Move all transactions in the finished transaction list to the pending transaction database, where they will wait for additional signatures from other voting pool members.
===Update Status===
<div style="float: right"><include iframe src="https://www.lucidchart.com/documents/embeddedchart/b4dcbb56-3b87-4f56-b249-dfd4276c7cc4" width="300" height="800" frameborder="0" scrolling="yes" /></div>
====Initial Conditions====
* Every outBailmentID in the [[Outputs (Voting Pool Wallet API)|output]] list provided has a non-nil <code>status</code> value in the [[withdrawal status]] object.
# Update the status for any split outBailments which exist:
## Split outBailments have more than one item in their <code>transactions</code> array and have an existing <code>status</code> value of "success".
## Change the <code>status</code> for all split outBailments from "success" to "split".
# Update the status for any partial outBailments which exist:
## Partial outBailments have existing <code>status</code> value of "partial-".
## Calculate the total missing value needed to satisfy the partial outputs.
## The missing value of an outBailment is the originally-requested size of the outBailment minus the sum of all outputs created to satisfy it (if any exist).
### Obtain a list of eligible inputs from the next un-thawed series and calculate their total value.
### If the eligible value of the next un-thawed series is greater than the total missing value, then the next un-thawed series is the target series.
#### If not, repeat the above process while keeping a running total of eligible value until a target series is located.
### Append the number of the target series to the <code>status</code> value for every partial series.
# Update the <code>nextinputstart</code> value.
## If the input stack is empty, <code>nextinputstart</code> is the first [[address identifier]] for the next un-thawed series.
## If the input stack is not empty, <code>nextinputstart</code> is the [[address identifier]] for the next input in the stack.
===Oversize Transaction===
<div style="float: right"><include iframe src="https://www.lucidchart.com/documents/embeddedchart/4abd7b4f-1079-48d7-af9d-d892433cd65a" width="300" height="400" frameborder="0" scrolling="yes" /></div>
An oversize transaction may occur while attempting to add inputs to satisfy an output which has been added to a transaction as part of the [[#Add Next Output|Add Next Output]] step.
Even with the worst case of high <code>m</code> and/or <code>n</code> values, inputs will never be large enough that a transaction can be oversized while attempting to add the first input to a transaction.
Oversize transactions will have one change output, at least one non-change output, and many inputs.
If an oversize has two or more non-change outputs, then Add Next Output was successful for the previous output. In this case, the transaction was valid before attempting to add the current output, so the current output should be removed from the current transaction and moved to the next one.
Otherwise, the transaction has become too large while adding its first non-change output. This can happen due to a series of small inputs, or a large output, or a combination of the two. In this case the [[outBailment]] can not be satisfied with a single transaction and must be split across multiple transactions.
====Initial Conditions====
* The Add Next Output procedure has created a transaction that exceeds size limits, in one of the following circumstances:
** Immediately upon adding an output to the transaction
*** The transaction contains other outputs which were added successfully. All inputs currently in the transaction are required to satisfy the previous output(s) before the output that failed.
** After adding the first input to the transaction to satisfy an output
*** The transaction contains other outputs which were added successfully. All inputs currently in the transaction are required to satisfy the previous output(s) before the output that failed.
** After having added at least one input to the transaction to satisfy an output without causing an oversize condition.
*** At least one of the inputs in this transaction are only required to satisfy the current output, not any previous outputs (if any exist).
# Count the number of non-change outputs in the transaction.
# If the number exceeds one, follow the [[#Rollback Last Output|Rollback Last Output]] procedure.
# If the number is one, follow the [[#Split Output|Split Output]] procedure.
===Rollback Last Output===
<div style="float: right"><include iframe src="https://www.lucidchart.com/documents/embeddedchart/81fbb6f6-b150-4a06-915e-7633b05da9af" width="300" height="600" frameborder="0" scrolling="yes" /></div>
This procedure is used if a transaction has exceeded the size limit, and can be brought back under the limit by removing the most-recently added output and the inputs needed to support it from the transaction
The removed inputs and outputs will be added to the beginning of a new transaction.
This procedure will remove one more input than is required to bring the transaction below size limits, and then add exactly one more. This will always result in a valid transaction because the only way to reach this procedure is by having successfully completed at least one [[#Add Next Output|Add Next Output]] cycle for the current transaction prior to adding an output or input that pushes the transaction over the size limit.
====Initial Conditions====
* An oversize transaction contains one or more outputs and their supporting inputs which do not exceed transaction size limits, and one output which does not.
* The number of inputs which are solely dedicated to satisfying the most recently-added output (not required for previous outputs) may be zero or more.
# Remove the most-recently added output from the transaction and return it to the output stack.
#* This will cause the output to be the first one added to the next transaction.
# Remove the most-recently added input from the transaction and return it to the input stack.
# If the sum of input values exceeds the sum of output values by an amount greater than the required transaction fee:
#* Continue removing inputs one at a time until the sum of the input values falls below the needed output + transaction fee value.
# Add the next input from the stack to the transaction.
# Perform the [[#Finalize Transaction|Finalize Transaction]] procedure.
# [[#Initialize Transaction|Initialize]] a new transaction.
===Split Output===
<div style="float: right"><include iframe src="https://www.lucidchart.com/documents/embeddedchart/c72ce59c-65c0-4a90-943a-c669bac5a737" width="300" height="1004" frameborder="0" scrolling="yes" /></div>
This procedure is used when, either due to a large output or small inputs, the inputs required to satisfy a single output can not fit within transaction size limits.
The outBailment will be split across two or more transactions.
====Initial Conditions====
* The transaction being constructed contains one change output, one non-change output, and many inputs.
* The transaction did not exceed any size limits until the most recent input was added.
# Remove the change output from the transaction.
# Decrement the value of the <code>nextchangestart</code> property of the [[withdrawal status]] object.
# Determine if the transaction still exceeds size limits.
## If yes, then remove the most-recently added input.
# Update the value of the output in the transaction to be the sum of the inputs minus the required transaction fee.
# Make a copy of the [[Outputs (Voting Pool Wallet API)|outputs]] object corresponding to this outBailmentID.
# Update the <code>amount</code> field of the copied output by subtracting the value determined in step 4.
# Push this new output to the output stack.
# Update the <code>status</code> field for this outBailmentID in the [[withdrawal status]] object to "partial-".
# Perform the [[#Finalize Transaction|Finalize Transaction]] Procedure.
# [[#Initialize Transaction|Initialize]] a new transaction.
===Insufficient Inputs===
<div style="float: right"><include iframe src="https://www.lucidchart.com/documents/embeddedchart/9b471e6e-0a62-408a-ad0e-a5d7c491aa43" width="150" height="500" frameborder="0" scrolling="yes" /></div>
This procedure is followed when the [[#Output List Initialization|Output List Initialization]] step estimated that that the input list was sufficient to fulfill the valid outputs but, due to change and/or transaction fees, one or more outputs can not be fulfilled.
====Initial Conditions====
* [[#Add Next Output|Add Next Output]] has been completed successfully at least once.
* An empty transaction skeleton has been created.
* At least one output remains in the stack, but the input stack is empty.
* Pop all outputs from the output stack and update their <code>status</code> property in the [[withdrawal status]] list to "partial-".
No further action is necessary. After completing this procedure the algorithm moves on to the [[#Finalize Transaction|Finalize Transaction]] procedure which will discard the empty transaction skeleton which would have otherwise contained the outputs which were processed in this procedure.
[[Category:Voting Pool Technical Specifications]]

Latest revision as of 14:55, 22 October 2014