create separate service for short term tx & block caching
This commit is contained in:
parent
befafaa60c
commit
7be3ed416e
@ -6,6 +6,7 @@ import { AppRoutingModule } from './app-routing.module';
|
|||||||
import { AppComponent } from './components/app/app.component';
|
import { AppComponent } from './components/app/app.component';
|
||||||
import { ElectrsApiService } from './services/electrs-api.service';
|
import { ElectrsApiService } from './services/electrs-api.service';
|
||||||
import { StateService } from './services/state.service';
|
import { StateService } from './services/state.service';
|
||||||
|
import { CacheService } from './services/cache.service';
|
||||||
import { EnterpriseService } from './services/enterprise.service';
|
import { EnterpriseService } from './services/enterprise.service';
|
||||||
import { WebsocketService } from './services/websocket.service';
|
import { WebsocketService } from './services/websocket.service';
|
||||||
import { AudioService } from './services/audio.service';
|
import { AudioService } from './services/audio.service';
|
||||||
@ -23,6 +24,7 @@ import { AppPreloadingStrategy } from './app.preloading-strategy';
|
|||||||
const providers = [
|
const providers = [
|
||||||
ElectrsApiService,
|
ElectrsApiService,
|
||||||
StateService,
|
StateService,
|
||||||
|
CacheService,
|
||||||
WebsocketService,
|
WebsocketService,
|
||||||
AudioService,
|
AudioService,
|
||||||
SeoService,
|
SeoService,
|
||||||
|
@ -138,7 +138,6 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
this.page = 1;
|
this.page = 1;
|
||||||
this.error = undefined;
|
this.error = undefined;
|
||||||
this.fees = undefined;
|
this.fees = undefined;
|
||||||
this.stateService.markBlock$.next({});
|
|
||||||
this.auditDataMissing = false;
|
this.auditDataMissing = false;
|
||||||
|
|
||||||
if (history.state.data && history.state.data.blockHeight) {
|
if (history.state.data && history.state.data.blockHeight) {
|
||||||
|
@ -5,6 +5,7 @@ import { specialBlocks } from '../../app.constants';
|
|||||||
import { BlockExtended } from '../../interfaces/node-api.interface';
|
import { BlockExtended } from '../../interfaces/node-api.interface';
|
||||||
import { Location } from '@angular/common';
|
import { Location } from '@angular/common';
|
||||||
import { config } from 'process';
|
import { config } from 'process';
|
||||||
|
import { CacheService } from 'src/app/services/cache.service';
|
||||||
|
|
||||||
interface BlockchainBlock extends BlockExtended {
|
interface BlockchainBlock extends BlockExtended {
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
@ -28,6 +29,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
emptyBlocks: BlockExtended[] = this.mountEmptyBlocks();
|
emptyBlocks: BlockExtended[] = this.mountEmptyBlocks();
|
||||||
markHeight: number;
|
markHeight: number;
|
||||||
blocksSubscription: Subscription;
|
blocksSubscription: Subscription;
|
||||||
|
blockPageSubscription: Subscription;
|
||||||
networkSubscription: Subscription;
|
networkSubscription: Subscription;
|
||||||
tabHiddenSubscription: Subscription;
|
tabHiddenSubscription: Subscription;
|
||||||
markBlockSubscription: Subscription;
|
markBlockSubscription: Subscription;
|
||||||
@ -56,6 +58,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public stateService: StateService,
|
public stateService: StateService,
|
||||||
|
public cacheService: CacheService,
|
||||||
private cd: ChangeDetectorRef,
|
private cd: ChangeDetectorRef,
|
||||||
private location: Location,
|
private location: Location,
|
||||||
) {
|
) {
|
||||||
@ -123,6 +126,12 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
this.cd.markForCheck();
|
this.cd.markForCheck();
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
this.blockPageSubscription = this.cacheService.loadedBlocks$.subscribe((block) => {
|
||||||
|
if (block.height <= this.height && block.height > this.height - this.count) {
|
||||||
|
this.onBlockLoaded(block);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.markBlockSubscription = this.stateService.markBlock$
|
this.markBlockSubscription = this.stateService.markBlock$
|
||||||
@ -151,6 +160,9 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
if (this.blocksSubscription) {
|
if (this.blocksSubscription) {
|
||||||
this.blocksSubscription.unsubscribe();
|
this.blocksSubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
|
if (this.blockPageSubscription) {
|
||||||
|
this.blockPageSubscription.unsubscribe();
|
||||||
|
}
|
||||||
this.networkSubscription.unsubscribe();
|
this.networkSubscription.unsubscribe();
|
||||||
this.tabHiddenSubscription.unsubscribe();
|
this.tabHiddenSubscription.unsubscribe();
|
||||||
this.markBlockSubscription.unsubscribe();
|
this.markBlockSubscription.unsubscribe();
|
||||||
@ -201,12 +213,9 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
while (this.blocks.length < Math.min(this.height + 1, this.count)) {
|
while (this.blocks.length < Math.min(this.height + 1, this.count)) {
|
||||||
const height = this.height - this.blocks.length;
|
const height = this.height - this.blocks.length;
|
||||||
if (height >= 0) {
|
if (height >= 0) {
|
||||||
// const block = this.cacheService.getCachedBlock(height) || null;
|
this.cacheService.loadBlock(height);
|
||||||
// if (!block) {
|
const block = this.cacheService.getCachedBlock(height) || null;
|
||||||
// this.cacheService.loadBlock(height);
|
this.blocks.push(block || {
|
||||||
// }
|
|
||||||
// this.blocks.push(block || {
|
|
||||||
this.blocks.push({
|
|
||||||
loading: true,
|
loading: true,
|
||||||
id: '',
|
id: '',
|
||||||
height,
|
height,
|
||||||
@ -236,6 +245,15 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBlockLoaded(block: BlockExtended) {
|
||||||
|
const blockIndex = this.height - block.height;
|
||||||
|
if (blockIndex >= 0 && blockIndex < this.blocks.length) {
|
||||||
|
this.blocks[blockIndex] = block;
|
||||||
|
this.blockStyles[blockIndex] = this.getStyleForBlock(block, blockIndex);
|
||||||
|
}
|
||||||
|
this.cd.markForCheck();
|
||||||
|
}
|
||||||
|
|
||||||
getStyleForBlock(block: BlockchainBlock, index: number, animateSlideStart: boolean = false) {
|
getStyleForBlock(block: BlockchainBlock, index: number, animateSlideStart: boolean = false) {
|
||||||
if (!block || block.loading) {
|
if (!block || block.loading) {
|
||||||
return this.getStyleForLoadingBlock(index, animateSlideStart);
|
return this.getStyleForLoadingBlock(index, animateSlideStart);
|
||||||
|
@ -46,6 +46,11 @@ export class StartComponent implements OnInit, OnDestroy {
|
|||||||
this.chainTip = height;
|
this.chainTip = height;
|
||||||
this.updatePages();
|
this.updatePages();
|
||||||
});
|
});
|
||||||
|
this.markBlockSubscription = this.stateService.markBlock$.subscribe((mark) => {
|
||||||
|
if (mark?.blockHeight != null) {
|
||||||
|
this.scrollToBlock(mark.blockHeight);
|
||||||
|
}
|
||||||
|
});
|
||||||
this.stateService.blocks$
|
this.stateService.blocks$
|
||||||
.subscribe((blocks: any) => {
|
.subscribe((blocks: any) => {
|
||||||
if (this.stateService.network !== '') {
|
if (this.stateService.network !== '') {
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
import { Transaction, Vout } from '../../interfaces/electrs.interface';
|
import { Transaction, Vout } from '../../interfaces/electrs.interface';
|
||||||
import { of, merge, Subscription, Observable, Subject, from } from 'rxjs';
|
import { of, merge, Subscription, Observable, Subject, from } from 'rxjs';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
|
import { CacheService } from '../../services/cache.service';
|
||||||
import { OpenGraphService } from '../../services/opengraph.service';
|
import { OpenGraphService } from '../../services/opengraph.service';
|
||||||
import { ApiService } from '../../services/api.service';
|
import { ApiService } from '../../services/api.service';
|
||||||
import { SeoService } from '../../services/seo.service';
|
import { SeoService } from '../../services/seo.service';
|
||||||
@ -45,6 +46,7 @@ export class TransactionPreviewComponent implements OnInit, OnDestroy {
|
|||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private electrsApiService: ElectrsApiService,
|
private electrsApiService: ElectrsApiService,
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
|
private cacheService: CacheService,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
private openGraphService: OpenGraphService,
|
private openGraphService: OpenGraphService,
|
||||||
@ -97,7 +99,7 @@ export class TransactionPreviewComponent implements OnInit, OnDestroy {
|
|||||||
}),
|
}),
|
||||||
switchMap(() => {
|
switchMap(() => {
|
||||||
let transactionObservable$: Observable<Transaction>;
|
let transactionObservable$: Observable<Transaction>;
|
||||||
const cached = this.stateService.getTxFromCache(this.txId);
|
const cached = this.cacheService.getTxFromCache(this.txId);
|
||||||
if (cached && cached.fee !== -1) {
|
if (cached && cached.fee !== -1) {
|
||||||
transactionObservable$ = of(cached);
|
transactionObservable$ = of(cached);
|
||||||
} else {
|
} else {
|
||||||
|
@ -13,6 +13,7 @@ import {
|
|||||||
import { Transaction } from '../../interfaces/electrs.interface';
|
import { Transaction } from '../../interfaces/electrs.interface';
|
||||||
import { of, merge, Subscription, Observable, Subject, timer, combineLatest, from, throwError } from 'rxjs';
|
import { of, merge, Subscription, Observable, Subject, timer, combineLatest, from, throwError } from 'rxjs';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
|
import { CacheService } from '../../services/cache.service';
|
||||||
import { WebsocketService } from '../../services/websocket.service';
|
import { WebsocketService } from '../../services/websocket.service';
|
||||||
import { AudioService } from '../../services/audio.service';
|
import { AudioService } from '../../services/audio.service';
|
||||||
import { ApiService } from '../../services/api.service';
|
import { ApiService } from '../../services/api.service';
|
||||||
@ -74,6 +75,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
private relativeUrlPipe: RelativeUrlPipe,
|
private relativeUrlPipe: RelativeUrlPipe,
|
||||||
private electrsApiService: ElectrsApiService,
|
private electrsApiService: ElectrsApiService,
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
|
private cacheService: CacheService,
|
||||||
private websocketService: WebsocketService,
|
private websocketService: WebsocketService,
|
||||||
private audioService: AudioService,
|
private audioService: AudioService,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
@ -203,7 +205,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
}),
|
}),
|
||||||
switchMap(() => {
|
switchMap(() => {
|
||||||
let transactionObservable$: Observable<Transaction>;
|
let transactionObservable$: Observable<Transaction>;
|
||||||
const cached = this.stateService.getTxFromCache(this.txId);
|
const cached = this.cacheService.getTxFromCache(this.txId);
|
||||||
if (cached && cached.fee !== -1) {
|
if (cached && cached.fee !== -1) {
|
||||||
transactionObservable$ = of(cached);
|
transactionObservable$ = of(cached);
|
||||||
} else {
|
} else {
|
||||||
@ -302,7 +304,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
this.waitingForTransaction = false;
|
this.waitingForTransaction = false;
|
||||||
}
|
}
|
||||||
this.rbfTransaction = rbfTransaction;
|
this.rbfTransaction = rbfTransaction;
|
||||||
this.stateService.setTxCache([this.rbfTransaction]);
|
this.cacheService.setTxCache([this.rbfTransaction]);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.queryParamsSubscription = this.route.queryParams.subscribe((params) => {
|
this.queryParamsSubscription = this.route.queryParams.subscribe((params) => {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Component, OnInit, Input, ChangeDetectionStrategy, OnChanges, Output, EventEmitter, ChangeDetectorRef } from '@angular/core';
|
import { Component, OnInit, Input, ChangeDetectionStrategy, OnChanges, Output, EventEmitter, ChangeDetectorRef } from '@angular/core';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
|
import { CacheService } from '../../services/cache.service';
|
||||||
import { Observable, ReplaySubject, BehaviorSubject, merge, Subscription } from 'rxjs';
|
import { Observable, ReplaySubject, BehaviorSubject, merge, Subscription } from 'rxjs';
|
||||||
import { Outspend, Transaction, Vin, Vout } from '../../interfaces/electrs.interface';
|
import { Outspend, Transaction, Vin, Vout } from '../../interfaces/electrs.interface';
|
||||||
import { ElectrsApiService } from '../../services/electrs-api.service';
|
import { ElectrsApiService } from '../../services/electrs-api.service';
|
||||||
@ -44,6 +45,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public stateService: StateService,
|
public stateService: StateService,
|
||||||
|
private cacheService: CacheService,
|
||||||
private electrsApiService: ElectrsApiService,
|
private electrsApiService: ElectrsApiService,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private assetsService: AssetsService,
|
private assetsService: AssetsService,
|
||||||
@ -123,7 +125,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.transactionsLength = this.transactions.length;
|
this.transactionsLength = this.transactions.length;
|
||||||
this.stateService.setTxCache(this.transactions);
|
this.cacheService.setTxCache(this.transactions);
|
||||||
|
|
||||||
this.transactions.forEach((tx) => {
|
this.transactions.forEach((tx) => {
|
||||||
tx['@voutLimit'] = true;
|
tx['@voutLimit'] = true;
|
||||||
|
105
frontend/src/app/services/cache.service.ts
Normal file
105
frontend/src/app/services/cache.service.ts
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { firstValueFrom, Subject, Subscription} from 'rxjs';
|
||||||
|
import { Transaction } from '../interfaces/electrs.interface';
|
||||||
|
import { BlockExtended } from '../interfaces/node-api.interface';
|
||||||
|
import { StateService } from './state.service';
|
||||||
|
import { ApiService } from './api.service';
|
||||||
|
|
||||||
|
const BLOCK_CACHE_SIZE = 50;
|
||||||
|
const KEEP_RECENT_BLOCKS = 50;
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class CacheService {
|
||||||
|
loadedBlocks$ = new Subject<BlockExtended>();
|
||||||
|
tip: number = 0;
|
||||||
|
|
||||||
|
txCache: { [txid: string]: Transaction } = {};
|
||||||
|
|
||||||
|
blockCache: { [height: number]: BlockExtended } = {};
|
||||||
|
blockLoading: { [height: number]: boolean } = {};
|
||||||
|
copiesInBlockQueue: { [height: number]: number } = {};
|
||||||
|
blockPriorities: number[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private stateService: StateService,
|
||||||
|
private apiService: ApiService,
|
||||||
|
) {
|
||||||
|
this.stateService.blocks$.subscribe(([block]) => {
|
||||||
|
this.addBlockToCache(block);
|
||||||
|
this.clearBlocks();
|
||||||
|
});
|
||||||
|
this.stateService.chainTip$.subscribe((height) => {
|
||||||
|
this.tip = height;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setTxCache(transactions) {
|
||||||
|
this.txCache = {};
|
||||||
|
transactions.forEach(tx => {
|
||||||
|
this.txCache[tx.txid] = tx;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getTxFromCache(txid) {
|
||||||
|
if (this.txCache && this.txCache[txid]) {
|
||||||
|
return this.txCache[txid];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addBlockToCache(block: BlockExtended) {
|
||||||
|
this.blockCache[block.height] = block;
|
||||||
|
this.bumpBlockPriority(block.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadBlock(height) {
|
||||||
|
if (!this.blockCache[height] && !this.blockLoading[height]) {
|
||||||
|
const chunkSize = 10;
|
||||||
|
const maxHeight = Math.ceil(height / chunkSize) * chunkSize;
|
||||||
|
for (let i = 0; i < chunkSize; i++) {
|
||||||
|
this.blockLoading[maxHeight - i] = true;
|
||||||
|
}
|
||||||
|
const result = await firstValueFrom(this.apiService.getBlocks$(maxHeight));
|
||||||
|
for (let i = 0; i < chunkSize; i++) {
|
||||||
|
delete this.blockLoading[maxHeight - i];
|
||||||
|
}
|
||||||
|
if (result && result.length) {
|
||||||
|
result.forEach(block => {
|
||||||
|
this.addBlockToCache(block);
|
||||||
|
this.loadedBlocks$.next(block);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.clearBlocks();
|
||||||
|
} else {
|
||||||
|
this.bumpBlockPriority(height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// increase the priority of a block, to delay removal
|
||||||
|
bumpBlockPriority(height) {
|
||||||
|
this.blockPriorities.push(height);
|
||||||
|
this.copiesInBlockQueue[height] = (this.copiesInBlockQueue[height] || 0) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove lowest priority blocks from the cache
|
||||||
|
clearBlocks() {
|
||||||
|
while (Object.keys(this.blockCache).length > (BLOCK_CACHE_SIZE + KEEP_RECENT_BLOCKS) && this.blockPriorities.length > KEEP_RECENT_BLOCKS) {
|
||||||
|
const height = this.blockPriorities.shift();
|
||||||
|
if (this.copiesInBlockQueue[height] > 1) {
|
||||||
|
this.copiesInBlockQueue[height]--;
|
||||||
|
} else if ((this.tip - height) < KEEP_RECENT_BLOCKS) {
|
||||||
|
this.bumpBlockPriority(height);
|
||||||
|
} else {
|
||||||
|
delete this.blockCache[height];
|
||||||
|
delete this.copiesInBlockQueue[height];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getCachedBlock(height) {
|
||||||
|
return this.blockCache[height];
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import { Inject, Injectable, PLATFORM_ID, LOCALE_ID } from '@angular/core';
|
import { Inject, Injectable, PLATFORM_ID, LOCALE_ID } from '@angular/core';
|
||||||
import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable } from 'rxjs';
|
import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable } from 'rxjs';
|
||||||
import { Block, Transaction } from '../interfaces/electrs.interface';
|
import { Transaction } from '../interfaces/electrs.interface';
|
||||||
import { IBackendInfo, MempoolBlock, MempoolBlockWithTransactions, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, TransactionStripped } from '../interfaces/websocket.interface';
|
import { IBackendInfo, MempoolBlock, MempoolBlockWithTransactions, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, TransactionStripped } from '../interfaces/websocket.interface';
|
||||||
import { BlockExtended, DifficultyAdjustment, OptimizedMempoolStats } from '../interfaces/node-api.interface';
|
import { BlockExtended, DifficultyAdjustment, OptimizedMempoolStats } from '../interfaces/node-api.interface';
|
||||||
import { Router, NavigationStart } from '@angular/router';
|
import { Router, NavigationStart } from '@angular/router';
|
||||||
@ -119,8 +119,6 @@ export class StateService {
|
|||||||
timeLtr: BehaviorSubject<boolean>;
|
timeLtr: BehaviorSubject<boolean>;
|
||||||
hideFlow: BehaviorSubject<boolean>;
|
hideFlow: BehaviorSubject<boolean>;
|
||||||
|
|
||||||
txCache: { [txid: string]: Transaction } = {};
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(PLATFORM_ID) private platformId: any,
|
@Inject(PLATFORM_ID) private platformId: any,
|
||||||
@Inject(LOCALE_ID) private locale: string,
|
@Inject(LOCALE_ID) private locale: string,
|
||||||
@ -275,28 +273,12 @@ export class StateService {
|
|||||||
return this.network === 'liquid' || this.network === 'liquidtestnet';
|
return this.network === 'liquid' || this.network === 'liquidtestnet';
|
||||||
}
|
}
|
||||||
|
|
||||||
setTxCache(transactions) {
|
|
||||||
this.txCache = {};
|
|
||||||
transactions.forEach(tx => {
|
|
||||||
this.txCache[tx.txid] = tx;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getTxFromCache(txid) {
|
|
||||||
if (this.txCache && this.txCache[txid]) {
|
|
||||||
return this.txCache[txid];
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resetChainTip() {
|
resetChainTip() {
|
||||||
this.latestBlockHeight = -1;
|
this.latestBlockHeight = -1;
|
||||||
this.chainTip$.next(-1);
|
this.chainTip$.next(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateChainTip(height) {
|
updateChainTip(height) {
|
||||||
console.log('updating chain tip to ', height);
|
|
||||||
if (height > this.latestBlockHeight) {
|
if (height > this.latestBlockHeight) {
|
||||||
this.latestBlockHeight = height;
|
this.latestBlockHeight = height;
|
||||||
this.chainTip$.next(height);
|
this.chainTip$.next(height);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user