-
Notifications
You must be signed in to change notification settings - Fork 0
/
block-cache.js
133 lines (112 loc) · 4 KB
/
block-cache.js
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
const cacheUtils = require('./cache-utils.js')
const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware')
// `<nil>` comes from https://github.com/ethereum/go-ethereum/issues/16925
const emptyValues = [undefined, null, '\u003cnil\u003e']
module.exports = createBlockCacheMiddleware
function createBlockCacheMiddleware(opts = {}) {
// validate options
const { blockTracker } = opts
if (!blockTracker) throw new Error('createBlockCacheMiddleware - No BlockTracker specified')
// create caching strategies
const blockCache = new BlockCacheStrategy()
const strategies = {
block: blockCache,
fork: blockCache,
}
return createAsyncMiddleware(async (req, res, next) => {
// check type and matching strategy
const type = cacheUtils.cacheTypeForPayload(req)
const strategy = strategies[type]
// If there's no strategy in place, pass it down the chain.
if (!strategy) return next()
// If the strategy can't cache this request, ignore it.
if (!strategy.canCache(req)) return next()
// get block reference (number or keyword)
let blockTag = cacheUtils.blockTagForPayload(req)
if (!blockTag) blockTag = 'latest'
// get exact block number
let requestedBlockNumber
if (blockTag === 'earliest') {
// this just exists for symmetry with "latest"
requestedBlockNumber = '0x00'
} else if (blockTag === 'latest') {
// fetch latest block number
const latestBlockNumber = await blockTracker.getLatestBlock()
// clear all cache before latest block
blockCache.clearBefore(latestBlockNumber)
requestedBlockNumber = latestBlockNumber
} else {
// We have a hex number
requestedBlockNumber = blockTag
}
// end on a hit, continue on a miss
const cacheResult = await strategy.get(req, requestedBlockNumber)
if (cacheResult === undefined) {
// cache miss
// wait for other middleware to handle request
await next()
// abort if other middleware did not fill in a result
if (emptyValues.includes(res.result)) return
// add result to cache
await strategy.set(req, requestedBlockNumber, res.result)
} else {
// fill in result from cache
res.result = cacheResult
}
})
}
//
// Cache Strategies
//
//
// BlockCacheStrategy
//
function BlockCacheStrategy() {
this.cache = {}
}
BlockCacheStrategy.prototype.getBlockCacheForPayload = function(payload, blockNumberHex) {
const blockNumber = Number.parseInt(blockNumberHex, 16)
let blockCache = this.cache[blockNumber]
// create new cache if necesary
if (!blockCache) {
const newCache = {}
this.cache[blockNumber] = newCache
blockCache = newCache
}
return blockCache
}
BlockCacheStrategy.prototype.get = async function(payload, requestedBlockNumber) {
// lookup block cache
const blockCache = this.getBlockCacheForPayload(payload, requestedBlockNumber)
if (!blockCache) return undefined
// lookup payload in block cache
const identifier = cacheUtils.cacheIdentifierForPayload(payload)
const cached = blockCache[identifier]
// may be undefined
return cached
}
BlockCacheStrategy.prototype.set = async function(payload, requestedBlockNumber, result) {
if (result !== undefined) {
const blockCache = this.getBlockCacheForPayload(payload, requestedBlockNumber)
const identifier = cacheUtils.cacheIdentifierForPayload(payload)
blockCache[identifier] = result
}
}
BlockCacheStrategy.prototype.canCache = function(payload) {
if (!cacheUtils.canCache(payload)) {
return false
}
const blockTag = cacheUtils.blockTagForPayload(payload)
const canCache = (blockTag !== 'pending')
return canCache
}
// removes all block caches with block number lower than `oldBlockHex`
BlockCacheStrategy.prototype.clearBefore = function(oldBlockHex){
const self = this
const oldBlockNumber = Number.parseInt(oldBlockHex, 16)
// clear old caches
Object.keys(self.cache)
.map(Number)
.filter(num => num < oldBlockNumber)
.forEach(num => delete self.cache[num])
}