-
Notifications
You must be signed in to change notification settings - Fork 1
/
BlockCache.ts
115 lines (90 loc) · 2.95 KB
/
BlockCache.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
113
114
115
import levelup, { LevelUp } from "levelup";
import leveldown from "leveldown";
import path from "path";
import { LightBlock } from "../../../../src/models/lightstreamer";
import { logThrottled } from "./logThrottled";
const KNOWN_KEYS = {
HEAD_SEQUENCE: "__HEAD_SEQUENCE__",
};
// Storing keys as zero-padded numbers to avoid lexicographic ordering.
// At one minute block times, this gives us ~1,900 years of blocks.
const KEY_LENGTH = 9;
export class BlockCache {
private db: LevelUp;
constructor() {
this.db = levelup(
leveldown(path.join(process.cwd(), "client-block-cache")),
);
}
public async getHeadSequence() {
try {
const headSequence = await this.db.get(KNOWN_KEYS.HEAD_SEQUENCE);
const asNumber = Number(headSequence);
if (isNaN(asNumber)) {
throw new Error("Head sequence is not a number");
}
return asNumber;
} catch (_err) {
return 0;
}
}
public async cacheBlock(block: LightBlock) {
const sequence = block.sequence;
logThrottled(`Caching block ${sequence}`, 100, block.sequence);
await this.db
.batch()
.put(this.encodeKey(sequence), LightBlock.encode(block).finish())
.put(KNOWN_KEYS.HEAD_SEQUENCE, sequence)
.write();
}
public encodeKey(num: number) {
return num.toString().padStart(KEY_LENGTH, "0");
}
public decodeKey(key: string) {
if (key in KNOWN_KEYS) {
return null;
}
return Number(key);
}
public async getBlockBySequence(sequence: number): Promise<LightBlock> {
const blockData = await this.db.get(this.encodeKey(sequence));
return LightBlock.decode(blockData);
}
public async handleReorg(lastMainChainBlock: LightBlock) {
const newHeadSequence = lastMainChainBlock.sequence;
const prevHeadSequence = await this.getHeadSequence();
if (newHeadSequence >= prevHeadSequence) {
return;
}
const keysToDelete: string[] = [];
// We're going to delete all the blocks starting at the new head sequence
// and going up to the previous head sequence.
for (let i = newHeadSequence; i <= prevHeadSequence; i++) {
keysToDelete.push(this.encodeKey(i));
}
await this.db.batch(keysToDelete.map((key) => ({ type: "del", key })));
await this.cacheBlock(lastMainChainBlock);
}
public async getBlockRange(
startSequence: number,
endSequence: number | null = null,
): Promise<LightBlock[]> {
if (endSequence === null) {
endSequence = await this.getHeadSequence();
}
if (startSequence > endSequence) {
throw new Error("Start sequence cannot be greater than end sequence");
}
const keys: string[] = [];
for (let i = startSequence; i <= endSequence; i++) {
keys.push(this.encodeKey(i));
}
const blocks: Buffer[] = await this.db.getMany(keys);
return blocks.map((block) => {
return LightBlock.decode(block);
});
}
public get createReadStream() {
return this.db.createReadStream;
}
}