forked from radotzki/eth-json-rpc-middleware
-
Notifications
You must be signed in to change notification settings - Fork 1
/
block-cache.js
156 lines (137 loc) · 4.63 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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
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 = {
perma: blockCache,
block: blockCache,
fork: blockCache,
}
return createAsyncMiddleware(async (req, res, next) => {
// allow cach to be skipped if so specified
if (req.skipCache) {
return 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.canCacheRequest(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()
// add result to cache
await strategy.set(req, requestedBlockNumber, res.result)
} else {
// fill in result from cache
res.result = cacheResult
}
})
}
//
// Cache Strategies
//
class BlockCacheStrategy {
constructor () {
this.cache = {}
}
getBlockCacheForPayload (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
}
async get (payload, requestedBlockNumber) {
// lookup block cache
const blockCache = this.getBlockCacheForPayload(payload, requestedBlockNumber)
if (!blockCache) return
// lookup payload in block cache
const identifier = cacheUtils.cacheIdentifierForPayload(payload, true)
const cached = blockCache[identifier]
// may be undefined
return cached
}
async set (payload, requestedBlockNumber, result) {
// check if we can cached this result
const canCache = this.canCacheResult(payload, result)
if (!canCache) return
// set the value in the cache
const blockCache = this.getBlockCacheForPayload(payload, requestedBlockNumber)
const identifier = cacheUtils.cacheIdentifierForPayload(payload, true)
blockCache[identifier] = result
}
canCacheRequest (payload) {
// check request method
if (!cacheUtils.canCache(payload)) {
return false
}
// check blockTag
const blockTag = cacheUtils.blockTagForPayload(payload)
if (blockTag === 'pending') {
return false
}
// can be cached
return true
}
canCacheResult (payload, result) {
// never cache empty values (e.g. undefined)
if (emptyValues.includes(result)) return
// check if transactions have block reference before caching
if (['eth_getTransactionByHash', 'eth_getTransactionReceipt'].includes(payload.method)) {
if (!result || !result.blockHash || result.blockHash === '0x0000000000000000000000000000000000000000000000000000000000000000') {
return false
}
}
// otherwise true
return true
}
// removes all block caches with block number lower than `oldBlockHex`
clearBefore (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])
}
}