Created
September 29, 2014 12:23
-
-
Save gsalgado/6c04ccc8606f6c065b7a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// createTx selects inputs (from the given slice of eligible utxos) | |
// whose amount are sufficient to fulfil all the desired outputs plus | |
// the mining fee. It then creates and returns a CreatedTx containing | |
// the selected inputs and the given outputs, validating it (using | |
// validateMsgTx) as well. | |
func createTx( | |
eligible []txstore.Credit, | |
outputs map[string]btcutil.Amount, | |
bs *keystore.BlockStamp, | |
feeIncrement btcutil.Amount, | |
changeAddress func(*keystore.BlockStamp) (btcutil.Address, error), | |
addInputs func(*btcwire.MsgTx, []txstore.Credit) error) (*CreatedTx, error) { | |
msgtx := btcwire.NewMsgTx() | |
var minAmount btcutil.Amount | |
for _, v := range outputs { | |
if v <= 0 { | |
return nil, ErrNonPositiveAmount | |
} | |
minAmount += v | |
} | |
if err := addOutputs(msgtx, outputs); err != nil { | |
return nil, err | |
} | |
// changeAddr is nil/zeroed until a change address is needed, and reused | |
// again in case a change utxo has already been chosen. | |
var changeAddr btcutil.Address | |
var changeIdx int | |
// Sort eligible inputs so that we first pick the ones with highest | |
// amount, thus reducing number of inputs. | |
sort.Sort(sort.Reverse(ByAmount(eligible))) | |
// Start by adding enough inputs to cover for the total amount of all | |
// desired outputs. | |
var inputs []txstore.Credit | |
totalAdded := btcutil.Amount(0) | |
i := 0 | |
for totalAdded < minAmount { | |
if i > len(eligible) { | |
return nil, InsufficientFunds{} | |
} | |
inputs = append(inputs, eligible[0]) | |
totalAdded += eligible[0].Amount() | |
i++ | |
} | |
if err := addInputs(msgtx, inputs); err != nil { | |
return nil, err | |
} | |
eligible = eligible[i:] | |
// Now estimate a fee and make sure the total amount of our inputs | |
// is enough for it and all outputs. If necessary we add more inputs, | |
// but in that case we also need to recalculate the fee. | |
fee := estimateFee(msgtx, inputs, bs.Height, feeIncrement) | |
i := 0 | |
for totalAdded < minAmount+fee { | |
if i > len(eligible) { | |
return nil, InsufficientFunds{} | |
} | |
input := eligible[0] | |
if err := addInputs(msgtx, []txstore.Credit{input}); err != nil { | |
return nil, err | |
} | |
inputs = append(inputs, input) | |
totalAdded += input.Amount() | |
fee = estimateFee(msgtx, inputs, bs.Height, feeIncrement) | |
i++ | |
} | |
eligible = eligible[i:] | |
// Check if there are leftover unspent outputs, and return coins back to | |
// a new address we own. | |
changeIdx = -1 | |
change := totalAdded - minAmount - fee | |
if change > 0 { | |
// Get a new change address if one has not already been found. | |
if changeAddr == nil { | |
changeAddr, err = changeAddress(bs) | |
if err != nil { | |
return nil, err | |
} | |
} | |
changeIdx, err = addChange(msgtx, change, changeAddr) | |
if err != nil { | |
return nil, err | |
} | |
for { | |
fee = estimateFee(msgtx, inputs, bs.Height, feeIncrement) | |
change := totalAdded - minAmount - fee | |
if change == 0 { | |
// TODO: remove the change output from msgtx | |
break | |
} else if change > 0 { | |
msgtx.TxOut[changeIdx].Amount = change | |
break | |
} | |
i := 0 | |
for totalAdded < minAmount+fee { | |
if i > len(eligible) { | |
return nil, InsufficientFunds{} | |
} | |
input := eligible[0] | |
if err := addInputs(msgtx, []txstore.Credit{input}); err != nil { | |
return nil, err | |
} | |
inputs = append(inputs, input) | |
totalAdded += input.Amount() | |
fee = estimateFee(msgtx, inputs, bs.Height, feeIncrement) | |
i++ | |
} | |
eligible = eligible[i:] | |
} | |
} | |
// Get the number of satoshis to increment fee by when searching for | |
// the minimum tx fee needed. | |
fee := btcutil.Amount(0) | |
for { | |
changeIdx = -1 | |
// Check if there are leftover unspent outputs, and return coins back to | |
// a new address we own. | |
change := totalAdded - minAmount - fee | |
if change > 0 { | |
// Get a new change address if one has not already been found. | |
if changeAddr == nil { | |
changeAddr, err = changeAddress(bs) | |
if err != nil { | |
return nil, err | |
} | |
} | |
changeIdx, err = addChange(msgtx, change, changeAddr) | |
if err != nil { | |
return nil, err | |
} | |
} | |
noFeeAllowed := false | |
if !cfg.DisallowFree { | |
noFeeAllowed = allowFree(bs.Height, inputs, msgtx.SerializeSize()) | |
} | |
if minFee := minimumFee(feeIncrement, msgtx, noFeeAllowed); fee < minFee { | |
fee = minFee | |
} else { | |
selectedInputs = inputs | |
break | |
} | |
} | |
if err := validateMsgTx(msgtx, selectedInputs); err != nil { | |
return nil, err | |
} | |
buf := bytes.Buffer{} | |
buf.Grow(msgtx.SerializeSize()) | |
if err := msgtx.BtcEncode(&buf, btcwire.ProtocolVersion); err != nil { | |
// Hitting OOM by growing or writing to a bytes.Buffer already | |
// panics, and all returned errors are unexpected. | |
panic(err) | |
} | |
info := &CreatedTx{ | |
tx: btcutil.NewTx(msgtx), | |
changeAddr: changeAddr, | |
changeIndex: changeIdx, | |
} | |
return info, nil | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment