-
Notifications
You must be signed in to change notification settings - Fork 11
/
hostmarket.go
228 lines (203 loc) · 7.18 KB
/
hostmarket.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
// Copyright 2019 DxChain, All rights reserved.
// Use of this source code is governed by an Apache
// License 2.0 that can be found in the LICENSE file.
package storagehostmanager
import (
"math"
"sort"
"sync"
"time"
"github.com/DxChainNetwork/godx/common"
"github.com/DxChainNetwork/godx/storage"
)
const (
// fields denotes the corresponding field in node info
fieldContractPrice = iota
fieldStoragePrice
fieldUploadPrice
fieldDownloadPrice
fieldDeposit
fieldMaxDeposit
)
// GetMarketPrice will return the market price. It will first try to get the value from
// cached prices. If cached prices need to be updated, prices are calculated and returned.
// Note that the function need to be protected by shm.lock for shm.initialScanFinished field.
func (shm *StorageHostManager) GetMarketPrice() storage.MarketPrice {
// If the initial scan has not finished, return the default host market price
// Since the shm has been locked when evaluating the host score, no lock is needed
// here.
if !shm.isInitialScanFinished() {
return defaultMarketPrice
}
return shm.cachedPrices.getPrices()
}
// UpdateMarketPriceLoop is a infinite loop to update the market price. The input mutex is locked in
// the inital status. After the first market price is updated, the lock will be unlocked to allow
// scan to continue.
func (shm *StorageHostManager) updateMarketPriceLoop(mutex *sync.Mutex) {
// Add to thread manager. If error happens directly return and no error reported.
if err := shm.tm.Add(); err != nil {
return
}
defer shm.tm.Done()
var once sync.Once
// Forever loop to update the prices
for {
// calculate the prices and update
prices := shm.calculateMarketPrice()
shm.cachedPrices.updatePrices(prices)
// unlock the mutex for once
once.Do(func() { mutex.Unlock() })
select {
// Return when stopped, continue when interval passed
case <-shm.tm.StopChan():
return
case <-time.After(priceUpdateInterval):
continue
}
}
}
// calculateMarketPrice calculate and return the market price
func (shm *StorageHostManager) calculateMarketPrice() storage.MarketPrice {
// get all host infos
infos := shm.ActiveStorageHosts()
// If there is no active hosts, return the default market price
if len(infos) == 0 {
return defaultMarketPrice
}
// change the list of hostInfo to the list of hostInfo pointers
ptrInfos := hostInfoListToPtrList(infos)
// calculate the market price and return
return storage.MarketPrice{
ContractPrice: getAveragePriceByField(ptrInfos, fieldContractPrice),
StoragePrice: getAveragePriceByField(ptrInfos, fieldStoragePrice),
UploadPrice: getAveragePriceByField(ptrInfos, fieldUploadPrice),
DownloadPrice: getAveragePriceByField(ptrInfos, fieldDownloadPrice),
Deposit: getAveragePriceByField(ptrInfos, fieldDeposit),
MaxDeposit: getAveragePriceByField(ptrInfos, fieldMaxDeposit),
}
}
// hostInfoListToPtrList change a list of hostInfo to a list of hostInfo pointers
func hostInfoListToPtrList(infos []storage.HostInfo) []*storage.HostInfo {
ptrs := make([]*storage.HostInfo, len(infos))
// copy the pointer to the pointer list. The pointer value to be appended need
// to be declared within the loop. More details please visit this blog:
// https://medium.com/codezillas/uh-ohs-in-go-slice-of-pointers-c0a30669feee
for i, info := range infos {
infoCopy := info
ptrs[i] = &infoCopy
}
return ptrs
}
// cachedPrices is the cache for pricing. The field is registered in storage host manager
// and not saved to persistence
type cachedPrices struct {
prices storage.MarketPrice
lock sync.RWMutex
}
// updatePrices update the prices in cachedPrices
func (cp *cachedPrices) updatePrices(prices storage.MarketPrice) {
cp.lock.Lock()
defer cp.lock.Unlock()
cp.prices = prices
}
// getPrices return the prices stored in cachedPrices
func (cp *cachedPrices) getPrices() storage.MarketPrice {
cp.lock.RLock()
defer cp.lock.RUnlock()
return cp.prices
}
// getAveragePriceByField get the average of the field specified by the input field
func getAveragePriceByField(infos []*storage.HostInfo, field int) common.BigInt {
sorter := newInfoPriceSorter(infos, field)
return getAverage(sorter)
}
// hostInfoPriceSorter is the structure for sorting for host infos. The structure implements sort interface
type hostInfoPriceSorter struct {
infos []*storage.HostInfo
field int
}
// newInfoPriceSorter returns a new hostInfoPriceSorter with the given infos and field
func newInfoPriceSorter(infos []*storage.HostInfo, field int) *hostInfoPriceSorter {
return &hostInfoPriceSorter{
infos: infos,
field: field,
}
}
// getAverage get the average price for infoSorter. The strategy is to neglect the lowest prices and
// highest prices, and aiming to get the average price for the mid range.
func getAverage(infoSorter *hostInfoPriceSorter) common.BigInt {
// Sort the info sorter
sort.Sort(infoSorter)
length := infoSorter.Len()
// Calculate the range we want to calculate for average
lowIndex := int(math.Floor(float64(length) * floorRatio))
highIndex := length - int(math.Floor(float64(length)*ceilRatio))
if highIndex == lowIndex {
return getMarketPriceByField(defaultMarketPrice, infoSorter.field)
}
// Calculate average
sum := common.BigInt0
for i := lowIndex; i != highIndex; i++ {
sum = sum.Add(infoSorter.getPrice(i))
}
average := sum.DivUint64(uint64(highIndex - lowIndex))
return average
}
// Len return the size of the list
func (infoSorter *hostInfoPriceSorter) Len() int {
return len(infoSorter.infos)
}
// Swap swap two elements specified by index
func (infoSorter *hostInfoPriceSorter) Swap(i, j int) {
infoSorter.infos[i], infoSorter.infos[j] = infoSorter.infos[j], infoSorter.infos[i]
}
// Less compare the two items of index i and j to determine whose related price is smaller
func (infoSorter *hostInfoPriceSorter) Less(i, j int) bool {
p1 := infoSorter.getPrice(i)
p2 := infoSorter.getPrice(j)
return p1.Cmp(p2) < 0
}
// getPrice get the price of specified field of specified indexed item. Notice the index is assumed to be within
// the range of [0, len(infoSorter.infos] - 1]
func (infoSorter *hostInfoPriceSorter) getPrice(index int) common.BigInt {
return getInfoPriceByField(infoSorter.infos[index], infoSorter.field)
}
// getInfoPriceByField get the price specified by field of a host info
func getInfoPriceByField(info *storage.HostInfo, field int) common.BigInt {
switch field {
case fieldContractPrice:
return info.ContractPrice
case fieldStoragePrice:
return info.StoragePrice
case fieldUploadPrice:
return info.UploadBandwidthPrice
case fieldDownloadPrice:
return info.DownloadBandwidthPrice
case fieldDeposit:
return info.Deposit
case fieldMaxDeposit:
return info.MaxDeposit
default:
}
return common.BigInt0
}
// getMarketPriceByField get the price specified by field of a storage market prices
func getMarketPriceByField(prices storage.MarketPrice, field int) common.BigInt {
switch field {
case fieldContractPrice:
return prices.ContractPrice
case fieldStoragePrice:
return prices.StoragePrice
case fieldUploadPrice:
return prices.UploadPrice
case fieldDownloadPrice:
return prices.DownloadPrice
case fieldDeposit:
return prices.Deposit
case fieldMaxDeposit:
return prices.MaxDeposit
default:
}
return common.BigInt0
}