-
-
Notifications
You must be signed in to change notification settings - Fork 454
Expand file tree
/
Copy pathstateContextCache.ts
More file actions
137 lines (122 loc) · 4.17 KB
/
stateContextCache.ts
File metadata and controls
137 lines (122 loc) · 4.17 KB
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import {toHexString} from "@chainsafe/ssz";
import {Epoch, RootHex} from "@lodestar/types";
import {CachedBeaconStateAllForks} from "@lodestar/state-transition";
import {routes} from "@lodestar/api";
import {Metrics} from "../../metrics/index.js";
import {StateCloneOpts} from "../regen/interface.js";
import {MapTracker} from "./mapMetrics.js";
import {BlockStateCache} from "./types.js";
const MAX_STATES = 3 * 32;
/**
* Old implementation of StateCache
* - Prune per checkpoint so number of states ranges from 96 to 128
* - Keep a separate head state to make sure it is always available
*/
export class StateContextCache implements BlockStateCache {
/**
* Max number of states allowed in the cache
*/
readonly maxStates: number;
private readonly cache: MapTracker<string, CachedBeaconStateAllForks>;
/** Epoch -> Set<blockRoot> */
private readonly epochIndex = new Map<Epoch, Set<string>>();
private readonly metrics: Metrics["stateCache"] | null | undefined;
/**
* Strong reference to prevent head state from being pruned.
* null if head state is being regen and not available at the moment.
*/
private head: {state: CachedBeaconStateAllForks; stateRoot: RootHex} | null = null;
constructor({maxStates = MAX_STATES, metrics}: {maxStates?: number; metrics?: Metrics | null}) {
this.maxStates = maxStates;
this.cache = new MapTracker(metrics?.stateCache);
if (metrics) {
this.metrics = metrics.stateCache;
metrics.stateCache.size.addCollect(() => metrics.stateCache.size.set(this.cache.size));
}
}
get(rootHex: RootHex, opts?: StateCloneOpts): CachedBeaconStateAllForks | null {
this.metrics?.lookups.inc();
const item = this.head?.stateRoot === rootHex ? this.head.state : this.cache.get(rootHex);
if (!item) {
return null;
}
this.metrics?.hits.inc();
this.metrics?.stateClonedCount.observe(item.clonedCount);
return item.clone(opts?.dontTransferCache);
}
add(item: CachedBeaconStateAllForks): void {
const key = toHexString(item.hashTreeRoot());
if (this.cache.get(key)) {
return;
}
this.metrics?.adds.inc();
this.cache.set(key, item);
const epoch = item.epochCtx.epoch;
const blockRoots = this.epochIndex.get(epoch);
if (blockRoots) {
blockRoots.add(key);
} else {
this.epochIndex.set(epoch, new Set([key]));
}
}
setHeadState(item: CachedBeaconStateAllForks | null): void {
if (item) {
const key = toHexString(item.hashTreeRoot());
this.head = {state: item, stateRoot: key};
} else {
this.head = null;
}
}
clear(): void {
this.cache.clear();
this.epochIndex.clear();
}
get size(): number {
return this.cache.size;
}
/**
* TODO make this more robust.
* Without more thought, this currently breaks our assumptions about recent state availablity
*/
prune(headStateRootHex: RootHex): void {
const keys = Array.from(this.cache.keys());
if (keys.length > this.maxStates) {
// object keys are stored in insertion order, delete keys starting from the front
for (const key of keys.slice(0, keys.length - this.maxStates)) {
if (key !== headStateRootHex) {
const item = this.cache.get(key);
if (item) {
this.epochIndex.get(item.epochCtx.epoch)?.delete(key);
this.cache.delete(key);
}
}
}
}
}
/**
* Prune per finalized epoch.
*/
deleteAllBeforeEpoch(finalizedEpoch: Epoch): void {
for (const epoch of this.epochIndex.keys()) {
if (epoch < finalizedEpoch) {
this.deleteAllEpochItems(epoch);
}
}
}
/** ONLY FOR DEBUGGING PURPOSES. For lodestar debug API */
dumpSummary(): routes.lodestar.StateCacheItem[] {
return Array.from(this.cache.entries()).map(([key, state]) => ({
slot: state.slot,
root: toHexString(state.hashTreeRoot()),
reads: this.cache.readCount.get(key) ?? 0,
lastRead: this.cache.lastRead.get(key) ?? 0,
checkpointState: false,
}));
}
private deleteAllEpochItems(epoch: Epoch): void {
for (const rootHex of this.epochIndex.get(epoch) || []) {
this.cache.delete(rootHex);
}
this.epochIndex.delete(epoch);
}
}