blockbook/common/internalstate.go

268 lines
7.7 KiB
Go

package common
import (
"encoding/json"
"sort"
"sync"
"time"
)
const (
// DbStateClosed means db was closed gracefully
DbStateClosed = uint32(iota)
// DbStateOpen means db is open or application died without closing the db
DbStateOpen
// DbStateInconsistent means db is in inconsistent state and cannot be used
DbStateInconsistent
)
// InternalStateColumn contains the data of a db column
type InternalStateColumn struct {
Name string `json:"name"`
Version uint32 `json:"version"`
Rows int64 `json:"rows"`
KeyBytes int64 `json:"keyBytes"`
ValueBytes int64 `json:"valueBytes"`
Updated time.Time `json:"updated"`
}
// BackendInfo is used to get information about blockchain
type BackendInfo struct {
BackendError string `json:"error,omitempty"`
Chain string `json:"chain,omitempty"`
Blocks int `json:"blocks,omitempty"`
Headers int `json:"headers,omitempty"`
BestBlockHash string `json:"bestBlockHash,omitempty"`
Difficulty string `json:"difficulty,omitempty"`
SizeOnDisk int64 `json:"sizeOnDisk,omitempty"`
Version string `json:"version,omitempty"`
Subversion string `json:"subversion,omitempty"`
ProtocolVersion string `json:"protocolVersion,omitempty"`
Timeoffset float64 `json:"timeOffset,omitempty"`
Warnings string `json:"warnings,omitempty"`
Consensus interface{} `json:"consensus,omitempty"`
}
// InternalState contains the data of the internal state
type InternalState struct {
mux sync.Mutex
Coin string `json:"coin"`
CoinShortcut string `json:"coinShortcut"`
CoinLabel string `json:"coinLabel"`
Host string `json:"host"`
DbState uint32 `json:"dbState"`
LastStore time.Time `json:"lastStore"`
// true if application is with flag --sync
SyncMode bool `json:"syncMode"`
InitialSync bool `json:"initialSync"`
IsSynchronized bool `json:"isSynchronized"`
BestHeight uint32 `json:"bestHeight"`
LastSync time.Time `json:"lastSync"`
BlockTimes []uint32 `json:"-"`
IsMempoolSynchronized bool `json:"isMempoolSynchronized"`
MempoolSize int `json:"mempoolSize"`
LastMempoolSync time.Time `json:"lastMempoolSync"`
DbColumns []InternalStateColumn `json:"dbColumns"`
UtxoChecked bool `json:"utxoChecked"`
BackendInfo BackendInfo `json:"-"`
}
// StartedSync signals start of synchronization
func (is *InternalState) StartedSync() {
is.mux.Lock()
defer is.mux.Unlock()
is.IsSynchronized = false
}
// FinishedSync marks end of synchronization, bestHeight specifies new best block height
func (is *InternalState) FinishedSync(bestHeight uint32) {
is.mux.Lock()
defer is.mux.Unlock()
is.IsSynchronized = true
is.BestHeight = bestHeight
is.LastSync = time.Now()
}
// UpdateBestHeight sets new best height, without changing IsSynchronized flag
func (is *InternalState) UpdateBestHeight(bestHeight uint32) {
is.mux.Lock()
defer is.mux.Unlock()
is.BestHeight = bestHeight
is.LastSync = time.Now()
}
// FinishedSyncNoChange marks end of synchronization in case no index update was necessary, it does not update lastSync time
func (is *InternalState) FinishedSyncNoChange() {
is.mux.Lock()
defer is.mux.Unlock()
is.IsSynchronized = true
}
// GetSyncState gets the state of synchronization
func (is *InternalState) GetSyncState() (bool, uint32, time.Time) {
is.mux.Lock()
defer is.mux.Unlock()
return is.IsSynchronized, is.BestHeight, is.LastSync
}
// StartedMempoolSync signals start of mempool synchronization
func (is *InternalState) StartedMempoolSync() {
is.mux.Lock()
defer is.mux.Unlock()
is.IsMempoolSynchronized = false
}
// FinishedMempoolSync marks end of mempool synchronization
func (is *InternalState) FinishedMempoolSync(mempoolSize int) {
is.mux.Lock()
defer is.mux.Unlock()
is.IsMempoolSynchronized = true
is.MempoolSize = mempoolSize
is.LastMempoolSync = time.Now()
}
// GetMempoolSyncState gets the state of mempool synchronization
func (is *InternalState) GetMempoolSyncState() (bool, time.Time, int) {
is.mux.Lock()
defer is.mux.Unlock()
return is.IsMempoolSynchronized, is.LastMempoolSync, is.MempoolSize
}
// AddDBColumnStats adds differences in column statistics to column stats
func (is *InternalState) AddDBColumnStats(c int, rowsDiff int64, keyBytesDiff int64, valueBytesDiff int64) {
is.mux.Lock()
defer is.mux.Unlock()
dc := &is.DbColumns[c]
dc.Rows += rowsDiff
dc.KeyBytes += keyBytesDiff
dc.ValueBytes += valueBytesDiff
dc.Updated = time.Now()
}
// SetDBColumnStats sets new values of column stats
func (is *InternalState) SetDBColumnStats(c int, rows int64, keyBytes int64, valueBytes int64) {
is.mux.Lock()
defer is.mux.Unlock()
dc := &is.DbColumns[c]
dc.Rows = rows
dc.KeyBytes = keyBytes
dc.ValueBytes = valueBytes
dc.Updated = time.Now()
}
// GetDBColumnStatValues gets stat values for given column
func (is *InternalState) GetDBColumnStatValues(c int) (int64, int64, int64) {
is.mux.Lock()
defer is.mux.Unlock()
if c < len(is.DbColumns) {
return is.DbColumns[c].Rows, is.DbColumns[c].KeyBytes, is.DbColumns[c].ValueBytes
}
return 0, 0, 0
}
// GetAllDBColumnStats returns stats for all columns
func (is *InternalState) GetAllDBColumnStats() []InternalStateColumn {
is.mux.Lock()
defer is.mux.Unlock()
return append(is.DbColumns[:0:0], is.DbColumns...)
}
// DBSizeTotal sums the computed sizes of all columns
func (is *InternalState) DBSizeTotal() int64 {
is.mux.Lock()
defer is.mux.Unlock()
total := int64(0)
for _, c := range is.DbColumns {
total += c.KeyBytes + c.ValueBytes
}
return total
}
// GetBlockTime returns block time if block found or 0
func (is *InternalState) GetBlockTime(height uint32) uint32 {
is.mux.Lock()
defer is.mux.Unlock()
if int(height) < len(is.BlockTimes) {
return is.BlockTimes[height]
}
return 0
}
// AppendBlockTime appends block time to BlockTimes
func (is *InternalState) AppendBlockTime(time uint32) {
is.mux.Lock()
defer is.mux.Unlock()
is.BlockTimes = append(is.BlockTimes, time)
}
// RemoveLastBlockTimes removes last times from BlockTimes
func (is *InternalState) RemoveLastBlockTimes(count int) {
is.mux.Lock()
defer is.mux.Unlock()
if len(is.BlockTimes) < count {
count = len(is.BlockTimes)
}
is.BlockTimes = is.BlockTimes[:len(is.BlockTimes)-count]
}
// GetBlockHeightOfTime returns block height of the first block with time greater or equal to the given time or MaxUint32 if no such block
func (is *InternalState) GetBlockHeightOfTime(time uint32) uint32 {
is.mux.Lock()
defer is.mux.Unlock()
height := sort.Search(len(is.BlockTimes), func(i int) bool { return time <= is.BlockTimes[i] })
if height == len(is.BlockTimes) {
return ^uint32(0)
}
// 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
max, height := height, height-20
if height < 0 {
height = 0
}
for ; height <= max; height++ {
if time <= is.BlockTimes[height] {
break
}
}
return uint32(height)
}
// SetBackendInfo sets new BackendInfo
func (is *InternalState) SetBackendInfo(bi *BackendInfo) {
is.mux.Lock()
defer is.mux.Unlock()
is.BackendInfo = *bi
}
// GetBackendInfo gets BackendInfo
func (is *InternalState) GetBackendInfo() BackendInfo {
is.mux.Lock()
defer is.mux.Unlock()
return is.BackendInfo
}
// Pack marshals internal state to json
func (is *InternalState) Pack() ([]byte, error) {
is.mux.Lock()
defer is.mux.Unlock()
is.LastStore = time.Now()
return json.Marshal(is)
}
// UnpackInternalState unmarshals internal state from json
func UnpackInternalState(buf []byte) (*InternalState, error) {
var is InternalState
if err := json.Unmarshal(buf, &is); err != nil {
return nil, err
}
return &is, nil
}