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 { ElectrsApiService } from './services/electrs-api.service';
|
||||
import { StateService } from './services/state.service';
|
||||
import { CacheService } from './services/cache.service';
|
||||
import { EnterpriseService } from './services/enterprise.service';
|
||||
import { WebsocketService } from './services/websocket.service';
|
||||
import { AudioService } from './services/audio.service';
|
||||
@ -23,6 +24,7 @@ import { AppPreloadingStrategy } from './app.preloading-strategy';
|
||||
const providers = [
|
||||
ElectrsApiService,
|
||||
StateService,
|
||||
CacheService,
|
||||
WebsocketService,
|
||||
AudioService,
|
||||
SeoService,
|
||||
|
@ -138,7 +138,6 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
this.page = 1;
|
||||
this.error = undefined;
|
||||
this.fees = undefined;
|
||||
this.stateService.markBlock$.next({});
|
||||
this.auditDataMissing = false;
|
||||
|
||||
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 { Location } from '@angular/common';
|
||||
import { config } from 'process';
|
||||
import { CacheService } from 'src/app/services/cache.service';
|
||||
|
||||
interface BlockchainBlock extends BlockExtended {
|
||||
loading?: boolean;
|
||||
@ -28,6 +29,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
emptyBlocks: BlockExtended[] = this.mountEmptyBlocks();
|
||||
markHeight: number;
|
||||
blocksSubscription: Subscription;
|
||||
blockPageSubscription: Subscription;
|
||||
networkSubscription: Subscription;
|
||||
tabHiddenSubscription: Subscription;
|
||||
markBlockSubscription: Subscription;
|
||||
@ -56,6 +58,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
constructor(
|
||||
public stateService: StateService,
|
||||
public cacheService: CacheService,
|
||||
private cd: ChangeDetectorRef,
|
||||
private location: Location,
|
||||
) {
|
||||
@ -123,6 +126,12 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
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$
|
||||
@ -151,6 +160,9 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
if (this.blocksSubscription) {
|
||||
this.blocksSubscription.unsubscribe();
|
||||
}
|
||||
if (this.blockPageSubscription) {
|
||||
this.blockPageSubscription.unsubscribe();
|
||||
}
|
||||
this.networkSubscription.unsubscribe();
|
||||
this.tabHiddenSubscription.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)) {
|
||||
const height = this.height - this.blocks.length;
|
||||
if (height >= 0) {
|
||||
// const block = this.cacheService.getCachedBlock(height) || null;
|
||||
// if (!block) {
|
||||
// this.cacheService.loadBlock(height);
|
||||
// }
|
||||
// this.blocks.push(block || {
|
||||
this.blocks.push({
|
||||
this.cacheService.loadBlock(height);
|
||||
const block = this.cacheService.getCachedBlock(height) || null;
|
||||
this.blocks.push(block || {
|
||||
loading: true,
|
||||
id: '',
|
||||
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) {
|
||||
if (!block || block.loading) {
|
||||
return this.getStyleForLoadingBlock(index, animateSlideStart);
|
||||
|
@ -46,6 +46,11 @@ export class StartComponent implements OnInit, OnDestroy {
|
||||
this.chainTip = height;
|
||||
this.updatePages();
|
||||
});
|
||||
this.markBlockSubscription = this.stateService.markBlock$.subscribe((mark) => {
|
||||
if (mark?.blockHeight != null) {
|
||||
this.scrollToBlock(mark.blockHeight);
|
||||
}
|
||||
});
|
||||
this.stateService.blocks$
|
||||
.subscribe((blocks: any) => {
|
||||
if (this.stateService.network !== '') {
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
import { Transaction, Vout } from '../../interfaces/electrs.interface';
|
||||
import { of, merge, Subscription, Observable, Subject, from } from 'rxjs';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { CacheService } from '../../services/cache.service';
|
||||
import { OpenGraphService } from '../../services/opengraph.service';
|
||||
import { ApiService } from '../../services/api.service';
|
||||
import { SeoService } from '../../services/seo.service';
|
||||
@ -45,6 +46,7 @@ export class TransactionPreviewComponent implements OnInit, OnDestroy {
|
||||
private route: ActivatedRoute,
|
||||
private electrsApiService: ElectrsApiService,
|
||||
private stateService: StateService,
|
||||
private cacheService: CacheService,
|
||||
private apiService: ApiService,
|
||||
private seoService: SeoService,
|
||||
private openGraphService: OpenGraphService,
|
||||
@ -97,7 +99,7 @@ export class TransactionPreviewComponent implements OnInit, OnDestroy {
|
||||
}),
|
||||
switchMap(() => {
|
||||
let transactionObservable$: Observable<Transaction>;
|
||||
const cached = this.stateService.getTxFromCache(this.txId);
|
||||
const cached = this.cacheService.getTxFromCache(this.txId);
|
||||
if (cached && cached.fee !== -1) {
|
||||
transactionObservable$ = of(cached);
|
||||
} else {
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
import { Transaction } from '../../interfaces/electrs.interface';
|
||||
import { of, merge, Subscription, Observable, Subject, timer, combineLatest, from, throwError } from 'rxjs';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { CacheService } from '../../services/cache.service';
|
||||
import { WebsocketService } from '../../services/websocket.service';
|
||||
import { AudioService } from '../../services/audio.service';
|
||||
import { ApiService } from '../../services/api.service';
|
||||
@ -74,6 +75,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
private relativeUrlPipe: RelativeUrlPipe,
|
||||
private electrsApiService: ElectrsApiService,
|
||||
private stateService: StateService,
|
||||
private cacheService: CacheService,
|
||||
private websocketService: WebsocketService,
|
||||
private audioService: AudioService,
|
||||
private apiService: ApiService,
|
||||
@ -203,7 +205,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
}),
|
||||
switchMap(() => {
|
||||
let transactionObservable$: Observable<Transaction>;
|
||||
const cached = this.stateService.getTxFromCache(this.txId);
|
||||
const cached = this.cacheService.getTxFromCache(this.txId);
|
||||
if (cached && cached.fee !== -1) {
|
||||
transactionObservable$ = of(cached);
|
||||
} else {
|
||||
@ -302,7 +304,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
this.waitingForTransaction = false;
|
||||
}
|
||||
this.rbfTransaction = rbfTransaction;
|
||||
this.stateService.setTxCache([this.rbfTransaction]);
|
||||
this.cacheService.setTxCache([this.rbfTransaction]);
|
||||
});
|
||||
|
||||
this.queryParamsSubscription = this.route.queryParams.subscribe((params) => {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Component, OnInit, Input, ChangeDetectionStrategy, OnChanges, Output, EventEmitter, ChangeDetectorRef } from '@angular/core';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { CacheService } from '../../services/cache.service';
|
||||
import { Observable, ReplaySubject, BehaviorSubject, merge, Subscription } from 'rxjs';
|
||||
import { Outspend, Transaction, Vin, Vout } from '../../interfaces/electrs.interface';
|
||||
import { ElectrsApiService } from '../../services/electrs-api.service';
|
||||
@ -44,6 +45,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
|
||||
constructor(
|
||||
public stateService: StateService,
|
||||
private cacheService: CacheService,
|
||||
private electrsApiService: ElectrsApiService,
|
||||
private apiService: ApiService,
|
||||
private assetsService: AssetsService,
|
||||
@ -123,7 +125,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
}
|
||||
|
||||
this.transactionsLength = this.transactions.length;
|
||||
this.stateService.setTxCache(this.transactions);
|
||||
this.cacheService.setTxCache(this.transactions);
|
||||
|
||||
this.transactions.forEach((tx) => {
|
||||
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 { 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 { BlockExtended, DifficultyAdjustment, OptimizedMempoolStats } from '../interfaces/node-api.interface';
|
||||
import { Router, NavigationStart } from '@angular/router';
|
||||
@ -119,8 +119,6 @@ export class StateService {
|
||||
timeLtr: BehaviorSubject<boolean>;
|
||||
hideFlow: BehaviorSubject<boolean>;
|
||||
|
||||
txCache: { [txid: string]: Transaction } = {};
|
||||
|
||||
constructor(
|
||||
@Inject(PLATFORM_ID) private platformId: any,
|
||||
@Inject(LOCALE_ID) private locale: string,
|
||||
@ -275,28 +273,12 @@ export class StateService {
|
||||
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() {
|
||||
this.latestBlockHeight = -1;
|
||||
this.chainTip$.next(-1);
|
||||
}
|
||||
|
||||
updateChainTip(height) {
|
||||
console.log('updating chain tip to ', height);
|
||||
if (height > this.latestBlockHeight) {
|
||||
this.latestBlockHeight = height;
|
||||
this.chainTip$.next(height);
|
||||
|
Loading…
x
Reference in New Issue
Block a user