Skip to content

Instantly share code, notes, and snippets.

@gsalgado
Created September 29, 2014 12:23
Show Gist options
  • Save gsalgado/6c04ccc8606f6c065b7a to your computer and use it in GitHub Desktop.
Save gsalgado/6c04ccc8606f6c065b7a to your computer and use it in GitHub Desktop.
// 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