forked from btcsuite/btcutil
-
Notifications
You must be signed in to change notification settings - Fork 0
/
coins.go
395 lines (342 loc) · 12.3 KB
/
coins.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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
package coinset
import (
"container/list"
"errors"
"sort"
"github.com/PointCoin/btcutil"
"github.com/PointCoin/btcwire"
)
// Coin represents a spendable transaction outpoint
type Coin interface {
Hash() *btcwire.ShaHash
Index() uint32
Value() btcutil.Amount
PkScript() []byte
NumConfs() int64
ValueAge() int64
}
// Coins represents a set of Coins
type Coins interface {
Coins() []Coin
}
// CoinSet is a utility struct for the modifications of a set of
// Coins that implements the Coins interface. To create a CoinSet,
// you must call NewCoinSet with nil for an empty set or a slice of
// coins as the initial contents.
//
// It is important to note that the all the Coins being added or removed
// from a CoinSet must have a constant ValueAge() during the use of
// the CoinSet, otherwise the cached values will be incorrect.
type CoinSet struct {
coinList *list.List
totalValue btcutil.Amount
totalValueAge int64
}
// Ensure that CoinSet is a Coins
var _ Coins = NewCoinSet(nil)
// NewCoinSet creates a CoinSet containing the coins provided.
// To create an empty CoinSet, you may pass null as the coins input parameter.
func NewCoinSet(coins []Coin) *CoinSet {
newCoinSet := &CoinSet{
coinList: list.New(),
totalValue: 0,
totalValueAge: 0,
}
for _, coin := range coins {
newCoinSet.PushCoin(coin)
}
return newCoinSet
}
// Coins returns a new slice of the coins contained in the set.
func (cs *CoinSet) Coins() []Coin {
coins := make([]Coin, cs.coinList.Len())
for i, e := 0, cs.coinList.Front(); e != nil; i, e = i+1, e.Next() {
coins[i] = e.Value.(Coin)
}
return coins
}
// TotalValue returns the total value of the coins in the set.
func (cs *CoinSet) TotalValue() (value btcutil.Amount) {
return cs.totalValue
}
// TotalValueAge returns the total value * number of confirmations
// of the coins in the set.
func (cs *CoinSet) TotalValueAge() (valueAge int64) {
return cs.totalValueAge
}
// Num returns the number of coins in the set
func (cs *CoinSet) Num() int {
return cs.coinList.Len()
}
// PushCoin adds a coin to the end of the list and updates
// the cached value amounts.
func (cs *CoinSet) PushCoin(c Coin) {
cs.coinList.PushBack(c)
cs.totalValue += c.Value()
cs.totalValueAge += c.ValueAge()
}
// PopCoin removes the last coin on the list and returns it.
func (cs *CoinSet) PopCoin() Coin {
back := cs.coinList.Back()
if back == nil {
return nil
}
return cs.removeElement(back)
}
// ShiftCoin removes the first coin on the list and returns it.
func (cs *CoinSet) ShiftCoin() Coin {
front := cs.coinList.Front()
if front == nil {
return nil
}
return cs.removeElement(front)
}
// removeElement updates the cached value amounts in the CoinSet,
// removes the element from the list, then returns the Coin that
// was removed to the caller.
func (cs *CoinSet) removeElement(e *list.Element) Coin {
c := e.Value.(Coin)
cs.coinList.Remove(e)
cs.totalValue -= c.Value()
cs.totalValueAge -= c.ValueAge()
return c
}
// NewMsgTxWithInputCoins takes the coins in the CoinSet and makes them
// the inputs to a new btcwire.MsgTx which is returned.
func NewMsgTxWithInputCoins(inputCoins Coins) *btcwire.MsgTx {
msgTx := btcwire.NewMsgTx()
coins := inputCoins.Coins()
msgTx.TxIn = make([]*btcwire.TxIn, len(coins))
for i, coin := range coins {
msgTx.TxIn[i] = &btcwire.TxIn{
PreviousOutPoint: btcwire.OutPoint{
Hash: *coin.Hash(),
Index: coin.Index(),
},
SignatureScript: nil,
Sequence: btcwire.MaxTxInSequenceNum,
}
}
return msgTx
}
var (
// ErrCoinsNoSelectionAvailable is returned when a CoinSelector believes there is no
// possible combination of coins which can meet the requirements provided to the selector.
ErrCoinsNoSelectionAvailable = errors.New("no coin selection possible")
)
// satisfiesTargetValue checks that the totalValue is either exactly the targetValue
// or is greater than the targetValue by at least the minChange amount.
func satisfiesTargetValue(targetValue, minChange, totalValue btcutil.Amount) bool {
return (totalValue == targetValue || totalValue >= targetValue+minChange)
}
// CoinSelector is an interface that wraps the CoinSelect method.
//
// CoinSelect will attempt to select a subset of the coins which has at
// least the targetValue amount. CoinSelect is not guaranteed to return a
// selection of coins even if the total value of coins given is greater
// than the target value.
//
// The exact choice of coins in the subset will be implementation specific.
//
// It is important to note that the Coins being used as inputs need to have
// a constant ValueAge() during the execution of CoinSelect.
type CoinSelector interface {
CoinSelect(targetValue btcutil.Amount, coins []Coin) (Coins, error)
}
// MinIndexCoinSelector is a CoinSelector that attempts to construct a
// selection of coins whose total value is at least targetValue and prefers
// any number of lower indexes (as in the ordered array) over higher ones.
type MinIndexCoinSelector struct {
MaxInputs int
MinChangeAmount btcutil.Amount
}
// CoinSelect will attempt to select coins using the algorithm described
// in the MinIndexCoinSelector struct.
func (s MinIndexCoinSelector) CoinSelect(targetValue btcutil.Amount, coins []Coin) (Coins, error) {
cs := NewCoinSet(nil)
for n := 0; n < len(coins) && n < s.MaxInputs; n++ {
cs.PushCoin(coins[n])
if satisfiesTargetValue(targetValue, s.MinChangeAmount, cs.TotalValue()) {
return cs, nil
}
}
return nil, ErrCoinsNoSelectionAvailable
}
// MinNumberCoinSelector is a CoinSelector that attempts to construct
// a selection of coins whose total value is at least targetValue
// that uses as few of the inputs as possible.
type MinNumberCoinSelector struct {
MaxInputs int
MinChangeAmount btcutil.Amount
}
// CoinSelect will attempt to select coins using the algorithm described
// in the MinNumberCoinSelector struct.
func (s MinNumberCoinSelector) CoinSelect(targetValue btcutil.Amount, coins []Coin) (Coins, error) {
sortedCoins := make([]Coin, 0, len(coins))
sortedCoins = append(sortedCoins, coins...)
sort.Sort(sort.Reverse(byAmount(sortedCoins)))
return (&MinIndexCoinSelector{
MaxInputs: s.MaxInputs,
MinChangeAmount: s.MinChangeAmount,
}).CoinSelect(targetValue, sortedCoins)
}
// MaxValueAgeCoinSelector is a CoinSelector that attempts to construct
// a selection of coins whose total value is at least targetValue
// that has as much input value-age as possible.
//
// This would be useful in the case where you want to maximize
// likelihood of the inclusion of your transaction in the next mined
// block.
type MaxValueAgeCoinSelector struct {
MaxInputs int
MinChangeAmount btcutil.Amount
}
// CoinSelect will attempt to select coins using the algorithm described
// in the MaxValueAgeCoinSelector struct.
func (s MaxValueAgeCoinSelector) CoinSelect(targetValue btcutil.Amount, coins []Coin) (Coins, error) {
sortedCoins := make([]Coin, 0, len(coins))
sortedCoins = append(sortedCoins, coins...)
sort.Sort(sort.Reverse(byValueAge(sortedCoins)))
return (&MinIndexCoinSelector{
MaxInputs: s.MaxInputs,
MinChangeAmount: s.MinChangeAmount,
}).CoinSelect(targetValue, sortedCoins)
}
// MinPriorityCoinSelector is a CoinSelector that attempts to construct
// a selection of coins whose total value is at least targetValue and
// whose average value-age per input is greater than MinAvgValueAgePerInput.
// If there is change, it must exceed MinChangeAmount to be a valid selection.
//
// When possible, MinPriorityCoinSelector will attempt to reduce the average
// input priority over the threshold, but no guarantees will be made as to
// minimality of the selection. The selection below is almost certainly
// suboptimal.
//
type MinPriorityCoinSelector struct {
MaxInputs int
MinChangeAmount btcutil.Amount
MinAvgValueAgePerInput int64
}
// CoinSelect will attempt to select coins using the algorithm described
// in the MinPriorityCoinSelector struct.
func (s MinPriorityCoinSelector) CoinSelect(targetValue btcutil.Amount, coins []Coin) (Coins, error) {
possibleCoins := make([]Coin, 0, len(coins))
possibleCoins = append(possibleCoins, coins...)
sort.Sort(byValueAge(possibleCoins))
// find the first coin with sufficient valueAge
cutoffIndex := -1
for i := 0; i < len(possibleCoins); i++ {
if possibleCoins[i].ValueAge() >= s.MinAvgValueAgePerInput {
cutoffIndex = i
break
}
}
if cutoffIndex < 0 {
return nil, ErrCoinsNoSelectionAvailable
}
// create sets of input coins that will obey minimum average valueAge
for i := cutoffIndex; i < len(possibleCoins); i++ {
possibleHighCoins := possibleCoins[cutoffIndex : i+1]
// choose a set of high-enough valueAge coins
highSelect, err := (&MinNumberCoinSelector{
MaxInputs: s.MaxInputs,
MinChangeAmount: s.MinChangeAmount,
}).CoinSelect(targetValue, possibleHighCoins)
if err != nil {
// attempt to add available low priority to make a solution
for numLow := 1; numLow <= cutoffIndex && numLow+(i-cutoffIndex) <= s.MaxInputs; numLow++ {
allHigh := NewCoinSet(possibleCoins[cutoffIndex : i+1])
newTargetValue := targetValue - allHigh.TotalValue()
newMaxInputs := allHigh.Num() + numLow
if newMaxInputs > numLow {
newMaxInputs = numLow
}
newMinAvgValueAge := ((s.MinAvgValueAgePerInput * int64(allHigh.Num()+numLow)) - allHigh.TotalValueAge()) / int64(numLow)
// find the minimum priority that can be added to set
lowSelect, err := (&MinPriorityCoinSelector{
MaxInputs: newMaxInputs,
MinChangeAmount: s.MinChangeAmount,
MinAvgValueAgePerInput: newMinAvgValueAge,
}).CoinSelect(newTargetValue, possibleCoins[0:cutoffIndex])
if err != nil {
continue
}
for _, coin := range lowSelect.Coins() {
allHigh.PushCoin(coin)
}
return allHigh, nil
}
// oh well, couldn't fix, try to add more high priority to the set.
} else {
extendedCoins := NewCoinSet(highSelect.Coins())
// attempt to lower priority towards target with lowest ones first
for n := 0; n < cutoffIndex; n++ {
if extendedCoins.Num() >= s.MaxInputs {
break
}
if possibleCoins[n].ValueAge() == 0 {
continue
}
extendedCoins.PushCoin(possibleCoins[n])
if extendedCoins.TotalValueAge()/int64(extendedCoins.Num()) < s.MinAvgValueAgePerInput {
extendedCoins.PopCoin()
continue
}
}
return extendedCoins, nil
}
}
return nil, ErrCoinsNoSelectionAvailable
}
type byValueAge []Coin
func (a byValueAge) Len() int { return len(a) }
func (a byValueAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byValueAge) Less(i, j int) bool { return a[i].ValueAge() < a[j].ValueAge() }
type byAmount []Coin
func (a byAmount) Len() int { return len(a) }
func (a byAmount) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byAmount) Less(i, j int) bool { return a[i].Value() < a[j].Value() }
// SimpleCoin defines a concrete instance of Coin that is backed by a
// btcutil.Tx, a specific outpoint index, and the number of confirmations
// that transaction has had.
type SimpleCoin struct {
Tx *btcutil.Tx
TxIndex uint32
TxNumConfs int64
}
// Ensure that SimpleCoin is a Coin
var _ Coin = &SimpleCoin{}
// Hash returns the hash value of the transaction on which the Coin is an output
func (c *SimpleCoin) Hash() *btcwire.ShaHash {
return c.Tx.Sha()
}
// Index returns the index of the output on the transaction which the Coin represents
func (c *SimpleCoin) Index() uint32 {
return c.TxIndex
}
// txOut returns the TxOut of the transaction the Coin represents
func (c *SimpleCoin) txOut() *btcwire.TxOut {
return c.Tx.MsgTx().TxOut[c.TxIndex]
}
// Value returns the value of the Coin
func (c *SimpleCoin) Value() btcutil.Amount {
return btcutil.Amount(c.txOut().Value)
}
// PkScript returns the outpoint script of the Coin.
//
// This can be used to determine what type of script the Coin uses
// and extract standard addresses if possible using
// txscript.ExtractPkScriptAddrs for example.
func (c *SimpleCoin) PkScript() []byte {
return c.txOut().PkScript
}
// NumConfs returns the number of confirmations that the transaction the Coin references
// has had.
func (c *SimpleCoin) NumConfs() int64 {
return c.TxNumConfs
}
// ValueAge returns the product of the value and the number of confirmations. This is
// used as an input to calculate the priority of the transaction.
func (c *SimpleCoin) ValueAge() int64 {
return c.TxNumConfs * int64(c.Value())
}