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  }