create separate service for short term tx & block caching
This commit is contained in:
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);
|
||||
|
||||
Reference in New Issue
Block a user