gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/accounting/accounting.go (about) 1 package accounting 2 3 import ( 4 "math" 5 "sort" 6 "sync" 7 "time" 8 9 "gitlab.com/NebulousLabs/errors" 10 "gitlab.com/NebulousLabs/threadgroup" 11 "gitlab.com/SkynetLabs/skyd/build" 12 "gitlab.com/SkynetLabs/skyd/skymodules" 13 "go.sia.tech/siad/modules" 14 "go.sia.tech/siad/persist" 15 ) 16 17 const ( 18 // DefaultEndRangeTime is the default end time used when one isn't provided 19 // by the user. 20 DefaultEndRangeTime = math.MaxInt64 21 ) 22 23 var ( 24 // ErrInvalidRange is the error returned if the end time is before the start 25 // time. 26 ErrInvalidRange = errors.New("invalid range, end must be after start") 27 28 // errNilDeps is the error returned when no dependencies are provided 29 errNilDeps = errors.New("dependencies cannot be nil") 30 31 // errNilPersistDir is the error returned when no persistDir is provided 32 errNilPersistDir = errors.New("persistDir cannot by blank") 33 34 // errNilWallet is the error returned when the wallet is nil 35 errNilWallet = errors.New("wallet cannot be nil") 36 37 // errNoEntriesFound is the error returned when no entries are found for 38 // a given range 39 errNoEntriesFound = errors.New("no entries found for given range") 40 ) 41 42 // Accounting contains the information needed for providing accounting 43 // information about a Sia node. 44 type Accounting struct { 45 // Modules whose accounting information is tracked 46 staticHost modules.Host 47 staticMiner modules.Miner 48 staticRenter skymodules.Renter 49 staticWallet modules.Wallet 50 51 // history is the entire persisted history of the accounting information 52 // 53 // NOTE: We only persist a small amount of data daily so this is OK and we are 54 // not concerned with this struct taking up much memory. 55 history []skymodules.AccountingInfo 56 57 // staticPersistDir is the accounting persist location on disk 58 staticPersistDir string 59 60 // Utilities 61 staticAOP *persist.AppendOnlyPersist 62 staticDeps modules.Dependencies 63 staticLog *persist.Logger 64 staticTG threadgroup.ThreadGroup 65 66 mu sync.Mutex 67 } 68 69 // NewCustomAccounting initializes the accounting module with custom 70 // dependencies 71 func NewCustomAccounting(h modules.Host, m modules.Miner, r skymodules.Renter, w modules.Wallet, persistDir string, deps modules.Dependencies) (*Accounting, error) { 72 // Check that at least the wallet is not nil 73 if w == nil { 74 return nil, errNilWallet 75 } 76 77 // Check required parameters 78 if persistDir == "" { 79 return nil, errNilPersistDir 80 } 81 if deps == nil { 82 return nil, errNilDeps 83 } 84 85 // Initialize the accounting 86 a := &Accounting{ 87 staticHost: h, 88 staticMiner: m, 89 staticRenter: r, 90 staticWallet: w, 91 92 staticPersistDir: persistDir, 93 94 staticDeps: deps, 95 } 96 97 // Initialize the persistence 98 err := a.initPersist() 99 if err != nil { 100 return nil, errors.AddContext(err, "unable to initialize the persistence") 101 } 102 103 // Launch background thread to persist the accounting information 104 if !a.staticDeps.Disrupt("DisablePersistLoop") { 105 go a.callThreadedPersistAccounting() 106 } 107 return a, nil 108 } 109 110 // Accounting returns the current accounting information 111 func (a *Accounting) Accounting(start, end int64) ([]skymodules.AccountingInfo, error) { 112 err := a.staticTG.Add() 113 if err != nil { 114 return nil, err 115 } 116 defer a.staticTG.Done() 117 118 // Check start and end range 119 if end < start { 120 return nil, ErrInvalidRange 121 } 122 123 // Update the accounting information 124 ai, err := a.callUpdateAccounting() 125 if err != nil { 126 return nil, errors.AddContext(err, "unable to update the accounting information") 127 } 128 129 // Return the requested range 130 a.mu.Lock() 131 history := append(a.history, ai) 132 a.mu.Unlock() 133 ais := accountingRange(history, start, end) 134 if len(ais) == 0 { 135 return nil, errNoEntriesFound 136 } 137 return ais, nil 138 } 139 140 // Close closes the accounting module 141 // 142 // NOTE: It will not call close on any of the modules it is tracking. Those 143 // modules are responsible for closing themselves independently. 144 func (a *Accounting) Close() error { 145 return a.staticTG.Stop() 146 } 147 148 // accountingRange returns a range of accounting information from a provided 149 // history 150 func accountingRange(history []skymodules.AccountingInfo, start, end int64) []skymodules.AccountingInfo { 151 // Sanity check 152 if end < start { 153 build.Critical(ErrInvalidRange) 154 return nil 155 } 156 157 // Find Start and end indexes 158 startIndex := sort.Search(len(history), func(i int) bool { 159 return history[i].Timestamp >= start 160 }) 161 endIndex := sort.Search(len(history), func(i int) bool { 162 return history[i].Timestamp > end 163 }) 164 165 // Return range 166 if endIndex == len(history) { 167 return history[startIndex:] 168 } 169 return history[startIndex:endIndex] 170 } 171 172 // callUpdateAccounting updates the accounting information 173 func (a *Accounting) callUpdateAccounting() (skymodules.AccountingInfo, error) { 174 var ai skymodules.AccountingInfo 175 ai.Timestamp = time.Now().Unix() 176 177 // Get Renter information 178 // 179 // NOTE: renter is optional so can be nil 180 var renterErr error 181 if a.staticRenter != nil { 182 var metrics skymodules.FinancialMetrics 183 metrics, renterErr = a.staticRenter.PeriodSpending() 184 if renterErr == nil { 185 spending := metrics.ContractorSpending 186 _, _, unspentUnallocated := spending.SpendingBreakdown() 187 ai.Renter.UnspentUnallocated = unspentUnallocated 188 ai.Renter.WithheldFunds = spending.WithheldFunds 189 } 190 } 191 192 // Get Wallet information 193 sc, sf, _, walletErr := a.staticWallet.ConfirmedBalance() 194 if walletErr == nil { 195 ai.Wallet.ConfirmedSiacoinBalance = sc 196 ai.Wallet.ConfirmedSiafundBalance = sf 197 } 198 199 return ai, errors.Compose(renterErr, walletErr) 200 } 201 202 // Enforce that Accounting satisfies the skymodules.Accounting interface. 203 var _ skymodules.Accounting = (*Accounting)(nil)