Implement Redis cache for block and mempool data
This commit is contained in:
parent
8cfa4ef1a1
commit
5138f9a254
158
backend/package-lock.json
generated
158
backend/package-lock.json
generated
@ -19,6 +19,7 @@
|
|||||||
"maxmind": "~4.3.11",
|
"maxmind": "~4.3.11",
|
||||||
"mysql2": "~3.5.2",
|
"mysql2": "~3.5.2",
|
||||||
"rust-gbt": "file:./rust-gbt",
|
"rust-gbt": "file:./rust-gbt",
|
||||||
|
"redis": "^4.6.6",
|
||||||
"socks-proxy-agent": "~7.0.0",
|
"socks-proxy-agent": "~7.0.0",
|
||||||
"typescript": "~4.9.3",
|
"typescript": "~4.9.3",
|
||||||
"ws": "~8.13.0"
|
"ws": "~8.13.0"
|
||||||
@ -1555,6 +1556,64 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@redis/bloom": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@redis/client": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@redis/client": {
|
||||||
|
"version": "1.5.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.7.tgz",
|
||||||
|
"integrity": "sha512-gaOBOuJPjK5fGtxSseaKgSvjiZXQCdLlGg9WYQst+/GRUjmXaiB5kVkeQMRtPc7Q2t93XZcJfBMSwzs/XS9UZw==",
|
||||||
|
"dependencies": {
|
||||||
|
"cluster-key-slot": "1.1.2",
|
||||||
|
"generic-pool": "3.9.0",
|
||||||
|
"yallist": "4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@redis/client/node_modules/yallist": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||||
|
},
|
||||||
|
"node_modules/@redis/graph": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-16yZWngxyXPd+MJxeSr0dqh2AIOi8j9yXKcKCwVaKDbH3HTuETpDVPcLujhFYVPtYrngSco31BUcSa9TH31Gqg==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@redis/client": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@redis/json": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-LUZE2Gdrhg0Rx7AN+cZkb1e6HjoSKaeeW8rYnt89Tly13GBI5eP4CwDVr+MY8BAYfCg4/N15OUrtLoona9uSgw==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@redis/client": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@redis/search": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-/cMfstG/fOh/SsE+4/BQGeuH/JJloeWuH+qJzM8dbxuWvdWibWAOAHHCZTMPhV3xIlH4/cUEIA8OV5QnYpaVoA==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@redis/client": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@redis/time-series": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-ThUIgo2U/g7cCuZavucQTQzA9g9JbDDY2f64u3AbAoz/8vE2lt2U37LamDUVChhaDA3IRT9R6VvJwqnUfTJzng==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@redis/client": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@sinclair/typebox": {
|
"node_modules/@sinclair/typebox": {
|
||||||
"version": "0.25.24",
|
"version": "0.25.24",
|
||||||
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz",
|
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz",
|
||||||
@ -2718,6 +2777,14 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cluster-key-slot": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/co": {
|
"node_modules/co": {
|
||||||
"version": "4.6.0",
|
"version": "4.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
||||||
@ -3678,6 +3745,14 @@
|
|||||||
"is-property": "^1.0.2"
|
"is-property": "^1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/generic-pool": {
|
||||||
|
"version": "3.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz",
|
||||||
|
"integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/gensync": {
|
"node_modules/gensync": {
|
||||||
"version": "1.0.0-beta.2",
|
"version": "1.0.0-beta.2",
|
||||||
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
||||||
@ -6577,6 +6652,19 @@
|
|||||||
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
|
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/redis": {
|
||||||
|
"version": "4.6.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis/-/redis-4.6.6.tgz",
|
||||||
|
"integrity": "sha512-aLs2fuBFV/VJ28oLBqYykfnhGGkFxvx0HdCEBYdJ99FFbSEMZ7c1nVKwR6ZRv+7bb7JnC0mmCzaqu8frgOYhpA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@redis/bloom": "1.2.0",
|
||||||
|
"@redis/client": "1.5.7",
|
||||||
|
"@redis/graph": "1.1.0",
|
||||||
|
"@redis/json": "1.0.4",
|
||||||
|
"@redis/search": "1.1.2",
|
||||||
|
"@redis/time-series": "1.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/require-directory": {
|
"node_modules/require-directory": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||||
@ -8704,6 +8792,53 @@
|
|||||||
"fastq": "^1.6.0"
|
"fastq": "^1.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@redis/bloom": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
|
"@redis/client": {
|
||||||
|
"version": "1.5.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.7.tgz",
|
||||||
|
"integrity": "sha512-gaOBOuJPjK5fGtxSseaKgSvjiZXQCdLlGg9WYQst+/GRUjmXaiB5kVkeQMRtPc7Q2t93XZcJfBMSwzs/XS9UZw==",
|
||||||
|
"requires": {
|
||||||
|
"cluster-key-slot": "1.1.2",
|
||||||
|
"generic-pool": "3.9.0",
|
||||||
|
"yallist": "4.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"yallist": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@redis/graph": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-16yZWngxyXPd+MJxeSr0dqh2AIOi8j9yXKcKCwVaKDbH3HTuETpDVPcLujhFYVPtYrngSco31BUcSa9TH31Gqg==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
|
"@redis/json": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-LUZE2Gdrhg0Rx7AN+cZkb1e6HjoSKaeeW8rYnt89Tly13GBI5eP4CwDVr+MY8BAYfCg4/N15OUrtLoona9uSgw==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
|
"@redis/search": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-/cMfstG/fOh/SsE+4/BQGeuH/JJloeWuH+qJzM8dbxuWvdWibWAOAHHCZTMPhV3xIlH4/cUEIA8OV5QnYpaVoA==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
|
"@redis/time-series": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-ThUIgo2U/g7cCuZavucQTQzA9g9JbDDY2f64u3AbAoz/8vE2lt2U37LamDUVChhaDA3IRT9R6VvJwqnUfTJzng==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"@sinclair/typebox": {
|
"@sinclair/typebox": {
|
||||||
"version": "0.25.24",
|
"version": "0.25.24",
|
||||||
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz",
|
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz",
|
||||||
@ -9604,6 +9739,11 @@
|
|||||||
"wrap-ansi": "^7.0.0"
|
"wrap-ansi": "^7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"cluster-key-slot": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="
|
||||||
|
},
|
||||||
"co": {
|
"co": {
|
||||||
"version": "4.6.0",
|
"version": "4.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
||||||
@ -10332,6 +10472,11 @@
|
|||||||
"is-property": "^1.0.2"
|
"is-property": "^1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"generic-pool": {
|
||||||
|
"version": "3.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz",
|
||||||
|
"integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g=="
|
||||||
|
},
|
||||||
"gensync": {
|
"gensync": {
|
||||||
"version": "1.0.0-beta.2",
|
"version": "1.0.0-beta.2",
|
||||||
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
||||||
@ -12454,6 +12599,19 @@
|
|||||||
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
|
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"redis": {
|
||||||
|
"version": "4.6.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis/-/redis-4.6.6.tgz",
|
||||||
|
"integrity": "sha512-aLs2fuBFV/VJ28oLBqYykfnhGGkFxvx0HdCEBYdJ99FFbSEMZ7c1nVKwR6ZRv+7bb7JnC0mmCzaqu8frgOYhpA==",
|
||||||
|
"requires": {
|
||||||
|
"@redis/bloom": "1.2.0",
|
||||||
|
"@redis/client": "1.5.7",
|
||||||
|
"@redis/graph": "1.1.0",
|
||||||
|
"@redis/json": "1.0.4",
|
||||||
|
"@redis/search": "1.1.2",
|
||||||
|
"@redis/time-series": "1.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"require-directory": {
|
"require-directory": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||||
|
@ -47,13 +47,14 @@
|
|||||||
"maxmind": "~4.3.11",
|
"maxmind": "~4.3.11",
|
||||||
"mysql2": "~3.5.2",
|
"mysql2": "~3.5.2",
|
||||||
"rust-gbt": "file:./rust-gbt",
|
"rust-gbt": "file:./rust-gbt",
|
||||||
|
"redis": "^4.6.6",
|
||||||
"socks-proxy-agent": "~7.0.0",
|
"socks-proxy-agent": "~7.0.0",
|
||||||
"typescript": "~4.9.3",
|
"typescript": "~4.9.3",
|
||||||
"ws": "~8.13.0"
|
"ws": "~8.13.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.21.3",
|
|
||||||
"@babel/code-frame": "^7.18.6",
|
"@babel/code-frame": "^7.18.6",
|
||||||
|
"@babel/core": "^7.21.3",
|
||||||
"@types/compression": "^1.7.2",
|
"@types/compression": "^1.7.2",
|
||||||
"@types/crypto-js": "^4.1.1",
|
"@types/crypto-js": "^4.1.1",
|
||||||
"@types/express": "^4.17.17",
|
"@types/express": "^4.17.17",
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
"AUTOMATIC_BLOCK_REINDEXING": false,
|
"AUTOMATIC_BLOCK_REINDEXING": false,
|
||||||
"POLL_RATE_MS": 3,
|
"POLL_RATE_MS": 3,
|
||||||
"CACHE_DIR": "__MEMPOOL_CACHE_DIR__",
|
"CACHE_DIR": "__MEMPOOL_CACHE_DIR__",
|
||||||
|
"CACHE_ENABLED": true,
|
||||||
"CLEAR_PROTECTION_MINUTES": 4,
|
"CLEAR_PROTECTION_MINUTES": 4,
|
||||||
"RECOMMENDED_FEE_PERCENTILE": 5,
|
"RECOMMENDED_FEE_PERCENTILE": 5,
|
||||||
"BLOCK_WEIGHT_UNITS": 6,
|
"BLOCK_WEIGHT_UNITS": 6,
|
||||||
@ -127,5 +128,9 @@
|
|||||||
"AUDIT": false,
|
"AUDIT": false,
|
||||||
"AUDIT_START_HEIGHT": 774000,
|
"AUDIT_START_HEIGHT": 774000,
|
||||||
"SERVERS": []
|
"SERVERS": []
|
||||||
|
},
|
||||||
|
"REDIS": {
|
||||||
|
"ENABLED": false,
|
||||||
|
"UNIX_SOCKET_PATH": "/tmp/redis.sock"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ describe('Mempool Backend Config', () => {
|
|||||||
AUTOMATIC_BLOCK_REINDEXING: false,
|
AUTOMATIC_BLOCK_REINDEXING: false,
|
||||||
POLL_RATE_MS: 2000,
|
POLL_RATE_MS: 2000,
|
||||||
CACHE_DIR: './cache',
|
CACHE_DIR: './cache',
|
||||||
|
CACHE_ENABLED: true,
|
||||||
CLEAR_PROTECTION_MINUTES: 20,
|
CLEAR_PROTECTION_MINUTES: 20,
|
||||||
RECOMMENDED_FEE_PERCENTILE: 50,
|
RECOMMENDED_FEE_PERCENTILE: 50,
|
||||||
BLOCK_WEIGHT_UNITS: 4000000,
|
BLOCK_WEIGHT_UNITS: 4000000,
|
||||||
@ -127,6 +128,11 @@ describe('Mempool Backend Config', () => {
|
|||||||
AUDIT_START_HEIGHT: 774000,
|
AUDIT_START_HEIGHT: 774000,
|
||||||
SERVERS: []
|
SERVERS: []
|
||||||
});
|
});
|
||||||
|
|
||||||
|
expect(config.REDIS).toStrictEqual({
|
||||||
|
ENABLED: false,
|
||||||
|
UNIX_SOCKET_PATH: ''
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -160,6 +166,8 @@ describe('Mempool Backend Config', () => {
|
|||||||
expect(config.PRICE_DATA_SERVER).toStrictEqual(fixture.PRICE_DATA_SERVER);
|
expect(config.PRICE_DATA_SERVER).toStrictEqual(fixture.PRICE_DATA_SERVER);
|
||||||
|
|
||||||
expect(config.EXTERNAL_DATA_SERVER).toStrictEqual(fixture.EXTERNAL_DATA_SERVER);
|
expect(config.EXTERNAL_DATA_SERVER).toStrictEqual(fixture.EXTERNAL_DATA_SERVER);
|
||||||
|
|
||||||
|
expect(config.REDIS).toStrictEqual(fixture.REDIS);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -26,6 +26,8 @@ import PricesRepository from '../repositories/PricesRepository';
|
|||||||
import priceUpdater from '../tasks/price-updater';
|
import priceUpdater from '../tasks/price-updater';
|
||||||
import chainTips from './chain-tips';
|
import chainTips from './chain-tips';
|
||||||
import websocketHandler from './websocket-handler';
|
import websocketHandler from './websocket-handler';
|
||||||
|
import redisCache from './redis-cache';
|
||||||
|
import rbfCache from './rbf-cache';
|
||||||
|
|
||||||
class Blocks {
|
class Blocks {
|
||||||
private blocks: BlockExtended[] = [];
|
private blocks: BlockExtended[] = [];
|
||||||
@ -804,10 +806,18 @@ class Blocks {
|
|||||||
if (this.newBlockCallbacks.length) {
|
if (this.newBlockCallbacks.length) {
|
||||||
this.newBlockCallbacks.forEach((cb) => cb(blockExtended, txIds, transactions));
|
this.newBlockCallbacks.forEach((cb) => cb(blockExtended, txIds, transactions));
|
||||||
}
|
}
|
||||||
if (!memPool.hasPriority() && (block.height % config.MEMPOOL.DISK_CACHE_BLOCK_INTERVAL === 0)) {
|
if (config.MEMPOOL.CACHE_ENABLED && !memPool.hasPriority() && (block.height % config.MEMPOOL.DISK_CACHE_BLOCK_INTERVAL === 0)) {
|
||||||
diskCache.$saveCacheToDisk();
|
diskCache.$saveCacheToDisk();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update Redis cache
|
||||||
|
if (config.REDIS.ENABLED) {
|
||||||
|
await redisCache.$updateBlocks(this.blocks);
|
||||||
|
await redisCache.$updateBlockSummaries(this.blockSummaries);
|
||||||
|
await redisCache.$removeTransactions(txIds);
|
||||||
|
await rbfCache.updateCache();
|
||||||
|
}
|
||||||
|
|
||||||
handledBlocks++;
|
handledBlocks++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ class DiskCache {
|
|||||||
};
|
};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
if (!cluster.isPrimary) {
|
if (!cluster.isPrimary || !config.MEMPOOL.CACHE_ENABLED) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
process.on('SIGINT', (e) => {
|
process.on('SIGINT', (e) => {
|
||||||
@ -39,7 +39,7 @@ class DiskCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async $saveCacheToDisk(sync: boolean = false): Promise<void> {
|
async $saveCacheToDisk(sync: boolean = false): Promise<void> {
|
||||||
if (!cluster.isPrimary) {
|
if (!cluster.isPrimary || !config.MEMPOOL.CACHE_ENABLED) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.isWritingCache) {
|
if (this.isWritingCache) {
|
||||||
@ -175,7 +175,7 @@ class DiskCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async $loadMempoolCache(): Promise<void> {
|
async $loadMempoolCache(): Promise<void> {
|
||||||
if (!fs.existsSync(DiskCache.FILE_NAME)) {
|
if (!config.MEMPOOL.CACHE_ENABLED || !fs.existsSync(DiskCache.FILE_NAME)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
@ -9,7 +9,7 @@ import loadingIndicators from './loading-indicators';
|
|||||||
import bitcoinClient from './bitcoin/bitcoin-client';
|
import bitcoinClient from './bitcoin/bitcoin-client';
|
||||||
import bitcoinSecondClient from './bitcoin/bitcoin-second-client';
|
import bitcoinSecondClient from './bitcoin/bitcoin-second-client';
|
||||||
import rbfCache from './rbf-cache';
|
import rbfCache from './rbf-cache';
|
||||||
import { IEsploraApi } from './bitcoin/esplora-api.interface';
|
import redisCache from './redis-cache';
|
||||||
|
|
||||||
class Mempool {
|
class Mempool {
|
||||||
private inSync: boolean = false;
|
private inSync: boolean = false;
|
||||||
@ -102,6 +102,10 @@ class Mempool {
|
|||||||
await this.$asyncMempoolChangedCallback(this.mempoolCache, count, [], []);
|
await this.$asyncMempoolChangedCallback(this.mempoolCache, count, [], []);
|
||||||
}
|
}
|
||||||
this.addToSpendMap(Object.values(this.mempoolCache));
|
this.addToSpendMap(Object.values(this.mempoolCache));
|
||||||
|
if (config.MEMPOOL.CACHE_ENABLED && config.REDIS.ENABLED) {
|
||||||
|
logger.debug('copying mempool from disk cache into Redis');
|
||||||
|
await redisCache.$addTransactions(Object.values(mempoolData));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async $reloadMempool(expectedCount: number): Promise<MempoolTransactionExtended[]> {
|
public async $reloadMempool(expectedCount: number): Promise<MempoolTransactionExtended[]> {
|
||||||
@ -318,6 +322,12 @@ class Mempool {
|
|||||||
loadingIndicators.setProgress('mempool', 100);
|
loadingIndicators.setProgress('mempool', 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update Redis cache
|
||||||
|
if (config.REDIS.ENABLED) {
|
||||||
|
await redisCache.$addTransactions(newTransactions);
|
||||||
|
await redisCache.$removeTransactions(deletedTransactions.map(tx => tx.txid));
|
||||||
|
}
|
||||||
|
|
||||||
const end = new Date().getTime();
|
const end = new Date().getTime();
|
||||||
const time = end - start;
|
const time = end - start;
|
||||||
logger.debug(`Mempool updated in ${time / 1000} seconds. New size: ${Object.keys(this.mempoolCache).length} (${diff > 0 ? '+' + diff : diff})`);
|
logger.debug(`Mempool updated in ${time / 1000} seconds. New size: ${Object.keys(this.mempoolCache).length} (${diff > 0 ? '+' + diff : diff})`);
|
||||||
|
140
backend/src/api/redis-cache.ts
Normal file
140
backend/src/api/redis-cache.ts
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
import { createClient } from 'redis';
|
||||||
|
import memPool from './mempool';
|
||||||
|
import blocks from './blocks';
|
||||||
|
import logger from '../logger';
|
||||||
|
import config from '../config';
|
||||||
|
import { BlockExtended, BlockSummary, TransactionExtended } from '../mempool.interfaces';
|
||||||
|
|
||||||
|
class RedisCache {
|
||||||
|
private client;
|
||||||
|
private connected = false;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
if (config.REDIS.ENABLED) {
|
||||||
|
const redisConfig = {
|
||||||
|
socket: {
|
||||||
|
path: config.REDIS.UNIX_SOCKET_PATH
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.client = createClient(redisConfig);
|
||||||
|
this.client.on('error', (e) => {
|
||||||
|
logger.err(`Error in Redis client: ${e instanceof Error ? e.message : e}`);
|
||||||
|
});
|
||||||
|
this.$ensureConnected();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async $ensureConnected(): Promise<void> {
|
||||||
|
if (!this.connected && config.REDIS.ENABLED) {
|
||||||
|
return this.client.connect().then(() => {
|
||||||
|
this.connected = true;
|
||||||
|
logger.info(`Redis client connected`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async $updateBlocks(blocks: BlockExtended[]) {
|
||||||
|
try {
|
||||||
|
await this.$ensureConnected();
|
||||||
|
await this.client.json.set('blocks', '$', blocks);
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn(`Failed to update blocks in Redis cache: ${e instanceof Error ? e.message : e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async $updateBlockSummaries(summaries: BlockSummary[]) {
|
||||||
|
try {
|
||||||
|
await this.$ensureConnected();
|
||||||
|
await this.client.json.set('block-summaries', '$', summaries);
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn(`Failed to update blocks in Redis cache: ${e instanceof Error ? e.message : e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async $addTransactions(newTransactions: TransactionExtended[]) {
|
||||||
|
try {
|
||||||
|
await this.$ensureConnected();
|
||||||
|
await Promise.all(newTransactions.map(tx => {
|
||||||
|
return this.client.json.set('tx:' + tx.txid, '$', tx);
|
||||||
|
}));
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn(`Failed to add ${newTransactions.length} transactions to Redis cache: ${e instanceof Error ? e.message : e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async $removeTransactions(transactions: string[]) {
|
||||||
|
try {
|
||||||
|
await this.$ensureConnected();
|
||||||
|
await Promise.all(transactions.map(txid => {
|
||||||
|
return this.client.del('tx:' + txid);
|
||||||
|
}));
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn(`Failed to remove ${transactions.length} transactions from Redis cache: ${e instanceof Error ? e.message : e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async $getBlocks(): Promise<BlockExtended[]> {
|
||||||
|
try {
|
||||||
|
await this.$ensureConnected();
|
||||||
|
return this.client.json.get('blocks');
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn(`Failed to retrieve blocks from Redis cache: ${e instanceof Error ? e.message : e}`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async $getBlockSummaries(): Promise<BlockSummary[]> {
|
||||||
|
try {
|
||||||
|
await this.$ensureConnected();
|
||||||
|
return this.client.json.get('block-summaries');
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn(`Failed to retrieve blocks from Redis cache: ${e instanceof Error ? e.message : e}`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async $getMempool(): Promise<{ [txid: string]: TransactionExtended }> {
|
||||||
|
const mempool = {};
|
||||||
|
try {
|
||||||
|
await this.$ensureConnected();
|
||||||
|
const keys = await this.client.keys('tx:*');
|
||||||
|
const promises: Promise<TransactionExtended[]>[] = [];
|
||||||
|
for (let i = 0; i < keys.length; i += 10000) {
|
||||||
|
const keySlice = keys.slice(i, i + 10000);
|
||||||
|
if (!keySlice.length) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
promises.push(this.client.json.mGet(keySlice, '$').then(chunk => {
|
||||||
|
for (const txs of chunk) {
|
||||||
|
for (const tx of txs) {
|
||||||
|
if (tx) {
|
||||||
|
mempool[tx.txid] = tx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
await Promise.all(promises);
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn(`Failed to retrieve mempool from Redis cache: ${e instanceof Error ? e.message : e}`);
|
||||||
|
}
|
||||||
|
return mempool;
|
||||||
|
}
|
||||||
|
|
||||||
|
async $loadCache() {
|
||||||
|
logger.info('Restoring mempool and blocks data from Redis cache');
|
||||||
|
// Load block data
|
||||||
|
const loadedBlocks = await this.$getBlocks();
|
||||||
|
const loadedBlockSummaries = await this.$getBlockSummaries();
|
||||||
|
// Load mempool
|
||||||
|
const loadedMempool = await this.$getMempool();
|
||||||
|
|
||||||
|
// Set loaded data
|
||||||
|
blocks.setBlocks(loadedBlocks || []);
|
||||||
|
blocks.setBlockSummaries(loadedBlockSummaries || []);
|
||||||
|
await memPool.$setMempool(loadedMempool);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new RedisCache();
|
@ -12,6 +12,7 @@ interface IConfig {
|
|||||||
API_URL_PREFIX: string;
|
API_URL_PREFIX: string;
|
||||||
POLL_RATE_MS: number;
|
POLL_RATE_MS: number;
|
||||||
CACHE_DIR: string;
|
CACHE_DIR: string;
|
||||||
|
CACHE_ENABLED: boolean;
|
||||||
CLEAR_PROTECTION_MINUTES: number;
|
CLEAR_PROTECTION_MINUTES: number;
|
||||||
RECOMMENDED_FEE_PERCENTILE: number;
|
RECOMMENDED_FEE_PERCENTILE: number;
|
||||||
BLOCK_WEIGHT_UNITS: number;
|
BLOCK_WEIGHT_UNITS: number;
|
||||||
@ -137,7 +138,11 @@ interface IConfig {
|
|||||||
AUDIT: boolean;
|
AUDIT: boolean;
|
||||||
AUDIT_START_HEIGHT: number;
|
AUDIT_START_HEIGHT: number;
|
||||||
SERVERS: string[];
|
SERVERS: string[];
|
||||||
}
|
},
|
||||||
|
REDIS: {
|
||||||
|
ENABLED: boolean;
|
||||||
|
UNIX_SOCKET_PATH: string;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaults: IConfig = {
|
const defaults: IConfig = {
|
||||||
@ -150,6 +155,7 @@ const defaults: IConfig = {
|
|||||||
'API_URL_PREFIX': '/api/v1/',
|
'API_URL_PREFIX': '/api/v1/',
|
||||||
'POLL_RATE_MS': 2000,
|
'POLL_RATE_MS': 2000,
|
||||||
'CACHE_DIR': './cache',
|
'CACHE_DIR': './cache',
|
||||||
|
'CACHE_ENABLED': true,
|
||||||
'CLEAR_PROTECTION_MINUTES': 20,
|
'CLEAR_PROTECTION_MINUTES': 20,
|
||||||
'RECOMMENDED_FEE_PERCENTILE': 50,
|
'RECOMMENDED_FEE_PERCENTILE': 50,
|
||||||
'BLOCK_WEIGHT_UNITS': 4000000,
|
'BLOCK_WEIGHT_UNITS': 4000000,
|
||||||
@ -275,7 +281,11 @@ const defaults: IConfig = {
|
|||||||
'AUDIT': false,
|
'AUDIT': false,
|
||||||
'AUDIT_START_HEIGHT': 774000,
|
'AUDIT_START_HEIGHT': 774000,
|
||||||
'SERVERS': [],
|
'SERVERS': [],
|
||||||
}
|
},
|
||||||
|
'REDIS': {
|
||||||
|
'ENABLED': false,
|
||||||
|
'UNIX_SOCKET_PATH': '',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
class Config implements IConfig {
|
class Config implements IConfig {
|
||||||
@ -296,6 +306,7 @@ class Config implements IConfig {
|
|||||||
EXTERNAL_DATA_SERVER: IConfig['EXTERNAL_DATA_SERVER'];
|
EXTERNAL_DATA_SERVER: IConfig['EXTERNAL_DATA_SERVER'];
|
||||||
MAXMIND: IConfig['MAXMIND'];
|
MAXMIND: IConfig['MAXMIND'];
|
||||||
REPLICATION: IConfig['REPLICATION'];
|
REPLICATION: IConfig['REPLICATION'];
|
||||||
|
REDIS: IConfig['REDIS'];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
const configs = this.merge(configFromFile, defaults);
|
const configs = this.merge(configFromFile, defaults);
|
||||||
@ -316,6 +327,7 @@ class Config implements IConfig {
|
|||||||
this.EXTERNAL_DATA_SERVER = configs.EXTERNAL_DATA_SERVER;
|
this.EXTERNAL_DATA_SERVER = configs.EXTERNAL_DATA_SERVER;
|
||||||
this.MAXMIND = configs.MAXMIND;
|
this.MAXMIND = configs.MAXMIND;
|
||||||
this.REPLICATION = configs.REPLICATION;
|
this.REPLICATION = configs.REPLICATION;
|
||||||
|
this.REDIS = configs.REDIS;
|
||||||
}
|
}
|
||||||
|
|
||||||
merge = (...objects: object[]): IConfig => {
|
merge = (...objects: object[]): IConfig => {
|
||||||
|
@ -41,6 +41,7 @@ import chainTips from './api/chain-tips';
|
|||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import v8 from 'v8';
|
import v8 from 'v8';
|
||||||
import { formatBytes, getBytesUnit } from './utils/format';
|
import { formatBytes, getBytesUnit } from './utils/format';
|
||||||
|
import redisCache from './api/redis-cache';
|
||||||
|
|
||||||
class Server {
|
class Server {
|
||||||
private wss: WebSocket.Server | undefined;
|
private wss: WebSocket.Server | undefined;
|
||||||
@ -122,7 +123,11 @@ class Server {
|
|||||||
await poolsUpdater.updatePoolsJson(); // Needs to be done before loading the disk cache because we sometimes wipe it
|
await poolsUpdater.updatePoolsJson(); // Needs to be done before loading the disk cache because we sometimes wipe it
|
||||||
await syncAssets.syncAssets$();
|
await syncAssets.syncAssets$();
|
||||||
if (config.MEMPOOL.ENABLED) {
|
if (config.MEMPOOL.ENABLED) {
|
||||||
|
if (config.MEMPOOL.CACHE_ENABLED) {
|
||||||
await diskCache.$loadMempoolCache();
|
await diskCache.$loadMempoolCache();
|
||||||
|
} else if (config.REDIS.ENABLED) {
|
||||||
|
await redisCache.$loadCache();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.STATISTICS.ENABLED && config.DATABASE.ENABLED && cluster.isPrimary) {
|
if (config.STATISTICS.ENABLED && config.DATABASE.ENABLED && cluster.isPrimary) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user