github.com/cryptohub-digital/blockbook@v0.3.5-0.20240403155730-99ab40b9104c/common/internalstate.go (about) 1 package common 2 3 import ( 4 "encoding/json" 5 "sort" 6 "sync" 7 "sync/atomic" 8 "time" 9 10 "github.com/golang/glog" 11 ) 12 13 const ( 14 // DbStateClosed means db was closed gracefully 15 DbStateClosed = uint32(iota) 16 // DbStateOpen means db is open or application died without closing the db 17 DbStateOpen 18 // DbStateInconsistent means db is in inconsistent state and cannot be used 19 DbStateInconsistent 20 ) 21 22 var inShutdown int32 23 24 // InternalStateColumn contains the data of a db column 25 type InternalStateColumn struct { 26 Name string `json:"name"` 27 Version uint32 `json:"version"` 28 Rows int64 `json:"rows"` 29 KeyBytes int64 `json:"keyBytes"` 30 ValueBytes int64 `json:"valueBytes"` 31 Updated time.Time `json:"updated"` 32 } 33 34 // BackendInfo is used to get information about blockchain 35 type BackendInfo struct { 36 BackendError string `json:"error,omitempty"` 37 Chain string `json:"chain,omitempty"` 38 Blocks int `json:"blocks,omitempty"` 39 Headers int `json:"headers,omitempty"` 40 BestBlockHash string `json:"bestBlockHash,omitempty"` 41 Difficulty string `json:"difficulty,omitempty"` 42 SizeOnDisk int64 `json:"sizeOnDisk,omitempty"` 43 Version string `json:"version,omitempty"` 44 Subversion string `json:"subversion,omitempty"` 45 ProtocolVersion string `json:"protocolVersion,omitempty"` 46 Timeoffset float64 `json:"timeOffset,omitempty"` 47 Warnings string `json:"warnings,omitempty"` 48 ConsensusVersion string `json:"consensus_version,omitempty"` 49 Consensus interface{} `json:"consensus,omitempty"` 50 } 51 52 // InternalState contains the data of the internal state 53 type InternalState struct { 54 mux sync.Mutex 55 56 Coin string `json:"coin"` 57 CoinShortcut string `json:"coinShortcut"` 58 CoinLabel string `json:"coinLabel"` 59 Host string `json:"host"` 60 61 DbState uint32 `json:"dbState"` 62 ExtendedIndex bool `json:"extendedIndex"` 63 64 LastStore time.Time `json:"lastStore"` 65 66 // true if application is with flag --sync 67 SyncMode bool `json:"syncMode"` 68 69 InitialSync bool `json:"initialSync"` 70 IsSynchronized bool `json:"isSynchronized"` 71 BestHeight uint32 `json:"bestHeight"` 72 StartSync time.Time `json:"-"` 73 LastSync time.Time `json:"lastSync"` 74 BlockTimes []uint32 `json:"-"` 75 AvgBlockPeriod uint32 `json:"-"` 76 77 IsMempoolSynchronized bool `json:"isMempoolSynchronized"` 78 MempoolSize int `json:"mempoolSize"` 79 LastMempoolSync time.Time `json:"lastMempoolSync"` 80 81 DbColumns []InternalStateColumn `json:"dbColumns"` 82 83 HasFiatRates bool `json:"-"` 84 HasTokenFiatRates bool `json:"-"` 85 HistoricalFiatRatesTime time.Time `json:"historicalFiatRatesTime"` 86 HistoricalTokenFiatRatesTime time.Time `json:"historicalTokenFiatRatesTime"` 87 88 EnableSubNewTx bool `json:"-"` 89 90 BackendInfo BackendInfo `json:"-"` 91 92 // database migrations 93 UtxoChecked bool `json:"utxoChecked"` 94 SortedAddressContracts bool `json:"sortedAddressContracts"` 95 } 96 97 // StartedSync signals start of synchronization 98 func (is *InternalState) StartedSync() { 99 is.mux.Lock() 100 defer is.mux.Unlock() 101 is.StartSync = time.Now().UTC() 102 is.IsSynchronized = false 103 } 104 105 // FinishedSync marks end of synchronization, bestHeight specifies new best block height 106 func (is *InternalState) FinishedSync(bestHeight uint32) { 107 is.mux.Lock() 108 defer is.mux.Unlock() 109 is.IsSynchronized = true 110 is.BestHeight = bestHeight 111 is.LastSync = time.Now().UTC() 112 } 113 114 // UpdateBestHeight sets new best height, without changing IsSynchronized flag 115 func (is *InternalState) UpdateBestHeight(bestHeight uint32) { 116 is.mux.Lock() 117 defer is.mux.Unlock() 118 is.BestHeight = bestHeight 119 is.LastSync = time.Now().UTC() 120 } 121 122 // FinishedSyncNoChange marks end of synchronization in case no index update was necessary, it does not update lastSync time 123 func (is *InternalState) FinishedSyncNoChange() { 124 is.mux.Lock() 125 defer is.mux.Unlock() 126 is.IsSynchronized = true 127 } 128 129 // GetSyncState gets the state of synchronization 130 func (is *InternalState) GetSyncState() (bool, uint32, time.Time, time.Time) { 131 is.mux.Lock() 132 defer is.mux.Unlock() 133 return is.IsSynchronized, is.BestHeight, is.LastSync, is.StartSync 134 } 135 136 // StartedMempoolSync signals start of mempool synchronization 137 func (is *InternalState) StartedMempoolSync() { 138 is.mux.Lock() 139 defer is.mux.Unlock() 140 is.IsMempoolSynchronized = false 141 } 142 143 // FinishedMempoolSync marks end of mempool synchronization 144 func (is *InternalState) FinishedMempoolSync(mempoolSize int) { 145 is.mux.Lock() 146 defer is.mux.Unlock() 147 is.IsMempoolSynchronized = true 148 is.MempoolSize = mempoolSize 149 is.LastMempoolSync = time.Now() 150 } 151 152 // GetMempoolSyncState gets the state of mempool synchronization 153 func (is *InternalState) GetMempoolSyncState() (bool, time.Time, int) { 154 is.mux.Lock() 155 defer is.mux.Unlock() 156 return is.IsMempoolSynchronized, is.LastMempoolSync, is.MempoolSize 157 } 158 159 // AddDBColumnStats adds differences in column statistics to column stats 160 func (is *InternalState) AddDBColumnStats(c int, rowsDiff int64, keyBytesDiff int64, valueBytesDiff int64) { 161 is.mux.Lock() 162 defer is.mux.Unlock() 163 dc := &is.DbColumns[c] 164 dc.Rows += rowsDiff 165 dc.KeyBytes += keyBytesDiff 166 dc.ValueBytes += valueBytesDiff 167 dc.Updated = time.Now() 168 } 169 170 // SetDBColumnStats sets new values of column stats 171 func (is *InternalState) SetDBColumnStats(c int, rows int64, keyBytes int64, valueBytes int64) { 172 is.mux.Lock() 173 defer is.mux.Unlock() 174 dc := &is.DbColumns[c] 175 dc.Rows = rows 176 dc.KeyBytes = keyBytes 177 dc.ValueBytes = valueBytes 178 dc.Updated = time.Now() 179 } 180 181 // GetDBColumnStatValues gets stat values for given column 182 func (is *InternalState) GetDBColumnStatValues(c int) (int64, int64, int64) { 183 is.mux.Lock() 184 defer is.mux.Unlock() 185 if c < len(is.DbColumns) { 186 return is.DbColumns[c].Rows, is.DbColumns[c].KeyBytes, is.DbColumns[c].ValueBytes 187 } 188 return 0, 0, 0 189 } 190 191 // GetAllDBColumnStats returns stats for all columns 192 func (is *InternalState) GetAllDBColumnStats() []InternalStateColumn { 193 is.mux.Lock() 194 defer is.mux.Unlock() 195 return append(is.DbColumns[:0:0], is.DbColumns...) 196 } 197 198 // DBSizeTotal sums the computed sizes of all columns 199 func (is *InternalState) DBSizeTotal() int64 { 200 is.mux.Lock() 201 defer is.mux.Unlock() 202 total := int64(0) 203 for _, c := range is.DbColumns { 204 total += c.KeyBytes + c.ValueBytes 205 } 206 return total 207 } 208 209 // GetBlockTime returns block time if block found or 0 210 func (is *InternalState) GetBlockTime(height uint32) uint32 { 211 is.mux.Lock() 212 defer is.mux.Unlock() 213 if int(height) < len(is.BlockTimes) { 214 return is.BlockTimes[height] 215 } 216 return 0 217 } 218 219 // GetLastBlockTime returns time of the last block 220 func (is *InternalState) GetLastBlockTime() uint32 { 221 is.mux.Lock() 222 defer is.mux.Unlock() 223 if len(is.BlockTimes) > 0 { 224 return is.BlockTimes[len(is.BlockTimes)-1] 225 } 226 return 0 227 } 228 229 // SetBlockTimes initializes BlockTimes array, returns AvgBlockPeriod 230 func (is *InternalState) SetBlockTimes(blockTimes []uint32) uint32 { 231 is.mux.Lock() 232 defer is.mux.Unlock() 233 is.BlockTimes = blockTimes 234 is.computeAvgBlockPeriod() 235 glog.Info("set ", len(is.BlockTimes), " block times, average block period ", is.AvgBlockPeriod, "s") 236 return is.AvgBlockPeriod 237 } 238 239 // AppendBlockTime appends block time to BlockTimes, returns AvgBlockPeriod 240 func (is *InternalState) AppendBlockTime(time uint32) uint32 { 241 is.mux.Lock() 242 defer is.mux.Unlock() 243 is.BlockTimes = append(is.BlockTimes, time) 244 is.computeAvgBlockPeriod() 245 return is.AvgBlockPeriod 246 } 247 248 // RemoveLastBlockTimes removes last times from BlockTimes 249 func (is *InternalState) RemoveLastBlockTimes(count int) { 250 is.mux.Lock() 251 defer is.mux.Unlock() 252 if len(is.BlockTimes) < count { 253 count = len(is.BlockTimes) 254 } 255 is.BlockTimes = is.BlockTimes[:len(is.BlockTimes)-count] 256 is.computeAvgBlockPeriod() 257 } 258 259 // GetBlockHeightOfTime returns block height of the first block with time greater or equal to the given time or MaxUint32 if no such block 260 func (is *InternalState) GetBlockHeightOfTime(time uint32) uint32 { 261 is.mux.Lock() 262 defer is.mux.Unlock() 263 height := sort.Search(len(is.BlockTimes), func(i int) bool { return time <= is.BlockTimes[i] }) 264 if height == len(is.BlockTimes) { 265 return ^uint32(0) 266 } 267 // as the block times can sometimes be out of order try 20 blocks lower to locate a block with the time greater or equal to the given time 268 max, height := height, height-20 269 if height < 0 { 270 height = 0 271 } 272 for ; height <= max; height++ { 273 if time <= is.BlockTimes[height] { 274 break 275 } 276 } 277 return uint32(height) 278 } 279 280 const avgBlockPeriodSample = 100 281 282 // Avg100BlocksPeriod returns average period of the last 100 blocks in seconds 283 func (is *InternalState) GetAvgBlockPeriod() uint32 { 284 is.mux.Lock() 285 defer is.mux.Unlock() 286 return is.AvgBlockPeriod 287 } 288 289 // computeAvgBlockPeriod returns computes average of the last 100 blocks in seconds 290 func (is *InternalState) computeAvgBlockPeriod() { 291 last := len(is.BlockTimes) - 1 292 first := last - avgBlockPeriodSample - 1 293 if first < 0 { 294 return 295 } 296 is.AvgBlockPeriod = (is.BlockTimes[last] - is.BlockTimes[first]) / avgBlockPeriodSample 297 } 298 299 // SetBackendInfo sets new BackendInfo 300 func (is *InternalState) SetBackendInfo(bi *BackendInfo) { 301 is.mux.Lock() 302 defer is.mux.Unlock() 303 is.BackendInfo = *bi 304 } 305 306 // GetBackendInfo gets BackendInfo 307 func (is *InternalState) GetBackendInfo() BackendInfo { 308 is.mux.Lock() 309 defer is.mux.Unlock() 310 return is.BackendInfo 311 } 312 313 // Pack marshals internal state to json 314 func (is *InternalState) Pack() ([]byte, error) { 315 is.mux.Lock() 316 defer is.mux.Unlock() 317 is.LastStore = time.Now() 318 return json.Marshal(is) 319 } 320 321 // UnpackInternalState unmarshals internal state from json 322 func UnpackInternalState(buf []byte) (*InternalState, error) { 323 var is InternalState 324 if err := json.Unmarshal(buf, &is); err != nil { 325 return nil, err 326 } 327 return &is, nil 328 } 329 330 // SetInShutdown sets the internal state to in shutdown state 331 func SetInShutdown() { 332 atomic.StoreInt32(&inShutdown, 1) 333 } 334 335 // IsInShutdown returns true if in application shutdown state 336 func IsInShutdown() bool { 337 return atomic.LoadInt32(&inShutdown) != 0 338 }