-
-
Notifications
You must be signed in to change notification settings - Fork 283
/
Copy pathcaching.ts
112 lines (94 loc) · 3.47 KB
/
caching.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import { Status } from '@trezor/blockchain-link-types/src/electrum';
import { ElectrumClient } from './electrum';
type Cache = {
[descriptor: string]: [Status, any];
};
type Statuses = {
[scripthash: string]: Status;
};
export class CachingElectrumClient extends ElectrumClient {
private readonly cache: Cache = {};
private readonly statuses: Statuses = {};
private cached = 0;
private total = 0;
private logTimer: ReturnType<typeof setInterval>;
constructor() {
super();
this.logTimer = setInterval(() => {
this.log(`Caching effectiveness: ${this.cached}/${this.total}`);
this.log('Subscription count: ', Object.keys(this.statuses).length);
}, 60000);
}
private async cacheRequest(status: Status, method: string, params: any[]) {
const descriptor = [method, ...params].join(':');
const cached = this.cache[descriptor];
if (cached) {
const [cachedStatus, cachedResponse] = cached;
if (cachedStatus === status) {
this.cached++;
return cachedResponse;
}
}
const response = await super.request(method, ...params);
this.cache[descriptor] = [status, response];
return response;
}
private async trySubscribe(scripthash: string): Promise<Status> {
const status = this.statuses[scripthash];
if (status !== undefined) {
// Already subscribed, just return latest status
return status;
}
// Subscribe to the new scripthash and store the status
const newStatus = await super.request('blockchain.scripthash.subscribe', scripthash);
this.statuses[scripthash] = newStatus;
return newStatus;
}
async request(method: string, ...params: any[]) {
this.total++;
switch (method) {
case 'blockchain.scripthash.get_history':
case 'blockchain.scripthash.get_balance':
case 'blockchain.scripthash.listunspent': {
const [scripthash] = params;
const status = await this.trySubscribe(scripthash);
return this.cacheRequest(status, method, params);
}
case 'blockchain.transaction.get': {
const curBlock = this.lastBlock?.hex;
if (curBlock === undefined) break;
return this.cacheRequest(curBlock, method, params);
}
case 'blockchain.scripthash.subscribe': {
const [scripthash] = params;
return this.trySubscribe(scripthash);
}
case 'blockchain.scripthash.unsubscribe': {
const [scripthash] = params;
delete this.statuses[scripthash];
return super.request(method, ...params);
}
default:
break;
}
return super.request(method, ...params);
}
protected response(response: any) {
const { method, params } = response;
// presence of 'method' field implies that it's a notification
switch (method) {
case 'blockchain.scripthash.subscribe': {
const [scripthash, status] = params;
this.statuses[scripthash] = status;
break;
}
default:
break;
}
super.response(response);
}
onClose() {
super.onClose();
clearInterval(this.logTimer);
}
}