forked from planetdecred/dcrlibwallet
-
Notifications
You must be signed in to change notification settings - Fork 0
/
utxo.go
164 lines (141 loc) · 5.92 KB
/
utxo.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
package dcrlibwallet
import (
"fmt"
"decred.org/dcrwallet/v2/errors"
"decred.org/dcrwallet/v2/wallet/txauthor"
"decred.org/dcrwallet/v2/wallet/txrules"
"decred.org/dcrwallet/v2/wallet/txsizes"
"github.com/decred/dcrd/chaincfg/v3"
"github.com/decred/dcrd/dcrutil/v4"
"github.com/decred/dcrd/txscript/v4"
"github.com/decred/dcrd/wire"
"github.com/planetdecred/dcrlibwallet/txhelper"
)
type nextAddressFunc func() (address string, err error)
func calculateChangeScriptSize(changeAddress string, chainParams *chaincfg.Params) (int, error) {
changeSource, err := txhelper.MakeTxChangeSource(changeAddress, chainParams)
if err != nil {
return 0, fmt.Errorf("change address error: %v", err)
}
return changeSource.ScriptSize(), nil
}
// ParseOutputsAndChangeDestination generates and returns TxOuts
// using the provided slice of transaction destinations.
// Any destination set to receive max amount is not included in the TxOuts returned,
// but is instead returned as a change destination.
// Returns an error if more than 1 max amount recipients identified or
// if any other error is encountered while processing the addresses and amounts.
func (tx *TxAuthor) ParseOutputsAndChangeDestination(txDestinations []TransactionDestination) ([]*wire.TxOut, int64, string, error) {
var outputs = make([]*wire.TxOut, 0)
var totalSendAmount int64
var maxAmountRecipientAddress string
for _, destination := range txDestinations {
if err := tx.validateSendAmount(destination.SendMax, destination.AtomAmount); err != nil {
return nil, 0, "", err
}
// check if multiple destinations are set to receive max amount
if destination.SendMax && maxAmountRecipientAddress != "" {
return nil, 0, "", fmt.Errorf("cannot send max amount to multiple recipients")
}
if destination.SendMax {
maxAmountRecipientAddress = destination.Address
continue // do not prepare a tx output for this destination
}
output, err := txhelper.MakeTxOutput(destination.Address, destination.AtomAmount, tx.sourceWallet.chainParams)
if err != nil {
return nil, 0, "", fmt.Errorf("make tx output error: %v", err)
}
totalSendAmount += output.Value
outputs = append(outputs, output)
}
return outputs, totalSendAmount, maxAmountRecipientAddress, nil
}
func (tx *TxAuthor) constructCustomTransaction() (*txauthor.AuthoredTx, error) {
// Used to generate an internal address for change,
// if no change destination is provided and
// no recipient is set to receive max amount.
nextInternalAddress := func() (string, error) {
ctx := tx.sourceWallet.shutdownContext()
addr, err := tx.sourceWallet.Internal().NewChangeAddress(ctx, tx.sourceAccountNumber)
if err != nil {
return "", err
}
return addr.String(), nil
}
return tx.newUnsignedTxUTXO(tx.inputs, tx.destinations, tx.changeDestination, nextInternalAddress)
}
func (tx *TxAuthor) newUnsignedTxUTXO(inputs []*wire.TxIn, sendDestinations []TransactionDestination, changeDestination *TransactionDestination,
nextInternalAddress nextAddressFunc) (*txauthor.AuthoredTx, error) {
outputs, totalSendAmount, maxAmountRecipientAddress, err := tx.ParseOutputsAndChangeDestination(sendDestinations)
if err != nil {
return nil, err
}
if maxAmountRecipientAddress != "" && changeDestination != nil {
return nil, errors.E(errors.Invalid, "no change is generated when sending max amount,"+
" change destinations must not be provided")
}
if maxAmountRecipientAddress == "" && changeDestination == nil {
// no change specified, generate new internal address to use as change (max amount recipient)
maxAmountRecipientAddress, err = nextInternalAddress()
if err != nil {
return nil, fmt.Errorf("error generating internal address to use as change: %s", err.Error())
}
}
var totalInputAmount int64
inputScriptSizes := make([]int, len(inputs))
inputScripts := make([][]byte, len(inputs))
for i, input := range inputs {
totalInputAmount += input.ValueIn
inputScriptSizes[i] = txsizes.RedeemP2PKHSigScriptSize
inputScripts[i] = input.SignatureScript
}
var changeScriptSize int
if maxAmountRecipientAddress != "" {
changeScriptSize, err = calculateChangeScriptSize(maxAmountRecipientAddress, tx.sourceWallet.chainParams)
} else {
changeScriptSize, err = calculateChangeScriptSize(changeDestination.Address, tx.sourceWallet.chainParams)
}
if err != nil {
return nil, err
}
maxSignedSize := txsizes.EstimateSerializeSize(inputScriptSizes, outputs, changeScriptSize)
maxRequiredFee := txrules.FeeForSerializeSize(txrules.DefaultRelayFeePerKb, maxSignedSize)
changeAmount := totalInputAmount - totalSendAmount - int64(maxRequiredFee)
if changeAmount < 0 {
return nil, errors.New(ErrInsufficientBalance)
}
if changeAmount != 0 && !txrules.IsDustAmount(dcrutil.Amount(changeAmount), changeScriptSize, txrules.DefaultRelayFeePerKb) {
if changeScriptSize > txscript.MaxScriptElementSize {
return nil, fmt.Errorf("script size exceed maximum bytes pushable to the stack")
}
if maxAmountRecipientAddress != "" {
outputs, err = tx.changeOutput(changeAmount, maxAmountRecipientAddress, outputs)
} else if changeDestination != nil {
outputs, err = tx.changeOutput(changeAmount, changeDestination.Address, outputs)
}
if err != nil {
return nil, fmt.Errorf("change address error: %v", err)
}
}
return &txauthor.AuthoredTx{
TotalInput: dcrutil.Amount(totalInputAmount),
EstimatedSignedSerializeSize: maxSignedSize,
Tx: &wire.MsgTx{
SerType: wire.TxSerializeFull,
Version: wire.TxVersion,
TxIn: inputs,
TxOut: outputs,
LockTime: 0,
Expiry: 0,
},
}, nil
}
func (tx *TxAuthor) changeOutput(changeAmount int64, maxAmountRecipientAddress string, outputs []*wire.TxOut) ([]*wire.TxOut, error) {
changeOutput, err := txhelper.MakeTxOutput(maxAmountRecipientAddress, changeAmount, tx.sourceWallet.chainParams)
if err != nil {
return nil, err
}
outputs = append(outputs, changeOutput)
txauthor.RandomizeOutputPosition(outputs, len(outputs)-1)
return outputs, nil
}