@subql/node-ethereum 1.10.1-3 → 2.0.1-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +65 -0
- package/README.md +3 -2
- package/dist/.tsbuildinfo +1 -1
- package/dist/configure/SubqueryProject.d.ts +9 -11
- package/dist/configure/SubqueryProject.js +19 -9
- package/dist/configure/SubqueryProject.js.map +1 -1
- package/dist/configure/configure.module.js +5 -7
- package/dist/configure/configure.module.js.map +1 -1
- package/dist/ethereum/api.connection.d.ts +11 -0
- package/dist/ethereum/api.connection.js +27 -0
- package/dist/ethereum/api.connection.js.map +1 -0
- package/dist/ethereum/api.ethereum.d.ts +18 -5
- package/dist/ethereum/api.ethereum.js +138 -43
- package/dist/ethereum/api.ethereum.js.map +1 -1
- package/dist/ethereum/api.service.ethereum.d.ts +11 -3
- package/dist/ethereum/api.service.ethereum.js +68 -18
- package/dist/ethereum/api.service.ethereum.js.map +1 -1
- package/dist/ethereum/api.service.ethereum.test.d.ts +1 -0
- package/dist/ethereum/api.service.ethereum.test.js +73 -0
- package/dist/ethereum/api.service.ethereum.test.js.map +1 -0
- package/dist/ethereum/block.ethereum.d.ts +1 -1
- package/dist/ethereum/block.ethereum.js +10 -12
- package/dist/ethereum/block.ethereum.js.map +1 -1
- package/dist/ethereum/ethers/json-rpc-batch-provider.d.ts +21 -0
- package/dist/ethereum/ethers/json-rpc-batch-provider.js +114 -0
- package/dist/ethereum/ethers/json-rpc-batch-provider.js.map +1 -0
- package/dist/ethereum/ethers/json-rpc-provider.d.ts +7 -0
- package/dist/ethereum/ethers/json-rpc-provider.js +68 -0
- package/dist/ethereum/ethers/json-rpc-provider.js.map +1 -0
- package/dist/ethereum/ethers/web/_version.d.ts +1 -0
- package/dist/ethereum/ethers/web/_version.js +6 -0
- package/dist/ethereum/ethers/web/_version.js.map +1 -0
- package/dist/ethereum/ethers/web/geturl.d.ts +3 -0
- package/dist/ethereum/ethers/web/geturl.js +116 -0
- package/dist/ethereum/ethers/web/geturl.js.map +1 -0
- package/dist/ethereum/ethers/web/index.d.ts +49 -0
- package/dist/ethereum/ethers/web/index.js +437 -0
- package/dist/ethereum/ethers/web/index.js.map +1 -0
- package/dist/ethereum/ethers/web/types.d.ts +26 -0
- package/dist/ethereum/ethers/web/types.js +4 -0
- package/dist/ethereum/ethers/web/types.js.map +1 -0
- package/dist/ethereum/safe-api.d.ts +34 -0
- package/dist/ethereum/safe-api.js +114 -0
- package/dist/ethereum/safe-api.js.map +1 -0
- package/dist/ethereum/utils.ethereum.d.ts +1 -1
- package/dist/ethereum/utils.ethereum.js +22 -87
- package/dist/ethereum/utils.ethereum.js.map +1 -1
- package/dist/indexer/blockDispatcher/block-dispatcher.service.d.ts +17 -0
- package/dist/indexer/blockDispatcher/block-dispatcher.service.js +55 -0
- package/dist/indexer/blockDispatcher/block-dispatcher.service.js.map +1 -0
- package/dist/indexer/blockDispatcher/ethereum-block-dispatcher.d.ts +4 -0
- package/dist/indexer/blockDispatcher/ethereum-block-dispatcher.js +5 -0
- package/dist/indexer/blockDispatcher/ethereum-block-dispatcher.js.map +1 -0
- package/dist/indexer/blockDispatcher/index.d.ts +4 -0
- package/dist/indexer/blockDispatcher/index.js +10 -0
- package/dist/indexer/blockDispatcher/index.js.map +1 -0
- package/dist/indexer/blockDispatcher/worker-block-dispatcher.service.d.ts +15 -0
- package/dist/indexer/blockDispatcher/worker-block-dispatcher.service.js +88 -0
- package/dist/indexer/blockDispatcher/worker-block-dispatcher.service.js.map +1 -0
- package/dist/indexer/dictionary.service.d.ts +1 -0
- package/dist/indexer/dictionary.service.js +22 -2
- package/dist/indexer/dictionary.service.js.map +1 -1
- package/dist/indexer/ds-processor.service.d.ts +1 -1
- package/dist/indexer/ds-processor.service.js +4 -0
- package/dist/indexer/ds-processor.service.js.map +1 -1
- package/dist/indexer/dynamic-ds.service.d.ts +3 -19
- package/dist/indexer/dynamic-ds.service.js +20 -72
- package/dist/indexer/dynamic-ds.service.js.map +1 -1
- package/dist/indexer/fetch.module.js +40 -12
- package/dist/indexer/fetch.module.js.map +1 -1
- package/dist/indexer/fetch.service.d.ts +16 -7
- package/dist/indexer/fetch.service.js +188 -78
- package/dist/indexer/fetch.service.js.map +1 -1
- package/dist/indexer/indexer.manager.d.ts +11 -17
- package/dist/indexer/indexer.manager.js +68 -71
- package/dist/indexer/indexer.manager.js.map +1 -1
- package/dist/indexer/indexer.module.js +32 -8
- package/dist/indexer/indexer.module.js.map +1 -1
- package/dist/indexer/project.service.d.ts +11 -8
- package/dist/indexer/project.service.js +76 -82
- package/dist/indexer/project.service.js.map +1 -1
- package/dist/indexer/sandbox.service.js +11 -2
- package/dist/indexer/sandbox.service.js.map +1 -1
- package/dist/indexer/types.d.ts +2 -1
- package/dist/indexer/types.js.map +1 -1
- package/dist/indexer/unfinalizedBlocks.service.d.ts +36 -0
- package/dist/indexer/unfinalizedBlocks.service.js +197 -0
- package/dist/indexer/unfinalizedBlocks.service.js.map +1 -0
- package/dist/indexer/unfinalizedBlocks.spec.d.ts +1 -0
- package/dist/indexer/unfinalizedBlocks.spec.js +195 -0
- package/dist/indexer/unfinalizedBlocks.spec.js.map +1 -0
- package/dist/indexer/worker/worker.d.ts +22 -6
- package/dist/indexer/worker/worker.js +16 -3
- package/dist/indexer/worker/worker.js.map +1 -1
- package/dist/indexer/worker/worker.service.d.ts +10 -6
- package/dist/indexer/worker/worker.service.js +23 -10
- package/dist/indexer/worker/worker.service.js.map +1 -1
- package/dist/indexer/worker/worker.unfinalizedBlocks.service.d.ts +11 -0
- package/dist/indexer/worker/worker.unfinalizedBlocks.service.js +32 -0
- package/dist/indexer/worker/worker.unfinalizedBlocks.service.js.map +1 -0
- package/dist/init.js +2 -2
- package/dist/init.js.map +1 -1
- package/dist/main.js +6 -1
- package/dist/main.js.map +1 -1
- package/dist/meta/meta.module.js +8 -0
- package/dist/meta/meta.module.js.map +1 -1
- package/dist/meta/meta.service.d.ts +18 -3
- package/dist/meta/meta.service.js +89 -5
- package/dist/meta/meta.service.js.map +1 -1
- package/dist/subcommands/forceClean.service.d.ts +3 -1
- package/dist/subcommands/forceClean.service.js +30 -6
- package/dist/subcommands/forceClean.service.js.map +1 -1
- package/dist/subcommands/reindex.init.js +6 -1
- package/dist/subcommands/reindex.init.js.map +1 -1
- package/dist/subcommands/reindex.module.js +20 -1
- package/dist/subcommands/reindex.module.js.map +1 -1
- package/dist/subcommands/reindex.service.d.ts +7 -3
- package/dist/subcommands/reindex.service.js +44 -50
- package/dist/subcommands/reindex.service.js.map +1 -1
- package/dist/subcommands/testing.init.d.ts +1 -0
- package/dist/subcommands/testing.init.js +34 -0
- package/dist/subcommands/testing.init.js.map +1 -0
- package/dist/subcommands/testing.module.d.ts +4 -0
- package/dist/subcommands/testing.module.js +77 -0
- package/dist/subcommands/testing.module.js.map +1 -0
- package/dist/subcommands/testing.service.d.ts +9 -0
- package/dist/subcommands/testing.service.js +43 -0
- package/dist/subcommands/testing.service.js.map +1 -0
- package/dist/utils/project.d.ts +3 -0
- package/dist/utils/project.js +45 -1
- package/dist/utils/project.js.map +1 -1
- package/dist/utils/reindex.d.ts +6 -0
- package/dist/utils/reindex.js +48 -0
- package/dist/utils/reindex.js.map +1 -0
- package/dist/utils/string.js +10 -2
- package/dist/utils/string.js.map +1 -1
- package/dist/yargs.d.ts +97 -49
- package/dist/yargs.js +130 -69
- package/dist/yargs.js.map +1 -1
- package/package.json +13 -11
- package/dist/indexer/worker/block-dispatcher.service.d.ts +0 -69
- package/dist/indexer/worker/block-dispatcher.service.js +0 -356
- package/dist/indexer/worker/block-dispatcher.service.js.map +0 -1
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright 2020-2022 OnFinality Limited authors & contributors
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
5
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
6
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
7
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
8
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
9
|
+
};
|
|
10
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
11
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
12
|
+
};
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.UnfinalizedBlocksService = exports.METADATA_LAST_FINALIZED_PROCESSED_KEY = exports.METADATA_UNFINALIZED_BLOCKS_KEY = void 0;
|
|
15
|
+
const common_1 = require("@nestjs/common");
|
|
16
|
+
const node_core_1 = require("@subql/node-core");
|
|
17
|
+
const lodash_1 = require("lodash");
|
|
18
|
+
const logger = (0, node_core_1.getLogger)('UnfinalizedBlocks');
|
|
19
|
+
exports.METADATA_UNFINALIZED_BLOCKS_KEY = 'unfinalizedBlocks';
|
|
20
|
+
exports.METADATA_LAST_FINALIZED_PROCESSED_KEY = 'lastFinalizedVerifiedHeight';
|
|
21
|
+
const UNFINALIZED_THRESHOLD = 200;
|
|
22
|
+
let UnfinalizedBlocksService = class UnfinalizedBlocksService {
|
|
23
|
+
constructor(apiService, nodeConfig, storeCache) {
|
|
24
|
+
this.apiService = apiService;
|
|
25
|
+
this.nodeConfig = nodeConfig;
|
|
26
|
+
this.storeCache = storeCache;
|
|
27
|
+
}
|
|
28
|
+
async init(reindex) {
|
|
29
|
+
logger.info(`Unfinalized blocks is ${this.nodeConfig.unfinalizedBlocks ? 'enabled' : 'disabled'}`);
|
|
30
|
+
// unfinalized blocks
|
|
31
|
+
this.unfinalizedBlocks = await this.getMetadataUnfinalizedBlocks();
|
|
32
|
+
this.lastCheckedBlockHeight = await this.getLastFinalizedVerifiedHeight();
|
|
33
|
+
this.finalizedHeader = await this.api.getBlockByHeightOrHash(await this.api.getFinalizedBlockHeight());
|
|
34
|
+
if (!this.nodeConfig.unfinalizedBlocks && this.unfinalizedBlocks.length) {
|
|
35
|
+
logger.info('Processing unfinalized blocks');
|
|
36
|
+
// Validate any previously unfinalized blocks
|
|
37
|
+
const rewindHeight = await this.processUnfinalizedBlocks(null);
|
|
38
|
+
if (rewindHeight !== null) {
|
|
39
|
+
logger.info(`Found un-finalized blocks from previous indexing but unverified, rolling back to last finalized block ${rewindHeight} `);
|
|
40
|
+
await reindex(rewindHeight);
|
|
41
|
+
logger.info(`Successful rewind to block ${rewindHeight}!`);
|
|
42
|
+
return rewindHeight;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
this.resetUnfinalizedBlocks();
|
|
46
|
+
this.resetLastFinalizedVerifiedHeight();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async processUnfinalizedBlocks(block) {
|
|
51
|
+
if (block) {
|
|
52
|
+
this.registerUnfinalizedBlock(block.number, block.hash);
|
|
53
|
+
}
|
|
54
|
+
const forkedHeader = await this.hasForked();
|
|
55
|
+
if (!forkedHeader) {
|
|
56
|
+
// Remove blocks that are now confirmed finalized
|
|
57
|
+
this.deleteFinalizedBlock();
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
// Get the last unfinalized block that is now finalized
|
|
61
|
+
return this.getLastCorrectFinalizedBlock(forkedHeader);
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
get api() {
|
|
66
|
+
return this.apiService.api;
|
|
67
|
+
}
|
|
68
|
+
get finalizedBlockNumber() {
|
|
69
|
+
return this.finalizedHeader.number;
|
|
70
|
+
}
|
|
71
|
+
registerFinalizedBlock(header) {
|
|
72
|
+
if (this.finalizedHeader && this.finalizedBlockNumber >= header.number) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
this.finalizedHeader = header;
|
|
76
|
+
}
|
|
77
|
+
registerUnfinalizedBlock(blockNumber, hash) {
|
|
78
|
+
if (blockNumber <= this.finalizedBlockNumber)
|
|
79
|
+
return;
|
|
80
|
+
// Ensure order
|
|
81
|
+
if (this.unfinalizedBlocks.length &&
|
|
82
|
+
(0, lodash_1.last)(this.unfinalizedBlocks)[0] + 1 !== blockNumber) {
|
|
83
|
+
logger.error('Unfinalized block is not sequential');
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
this.unfinalizedBlocks.push([blockNumber, hash]);
|
|
87
|
+
this.saveUnfinalizedBlocks(this.unfinalizedBlocks);
|
|
88
|
+
}
|
|
89
|
+
deleteFinalizedBlock() {
|
|
90
|
+
if (this.lastCheckedBlockHeight !== undefined &&
|
|
91
|
+
this.lastCheckedBlockHeight < this.finalizedBlockNumber) {
|
|
92
|
+
this.removeFinalized(this.finalizedBlockNumber);
|
|
93
|
+
this.saveLastFinalizedVerifiedHeight(this.finalizedBlockNumber);
|
|
94
|
+
this.saveUnfinalizedBlocks(this.unfinalizedBlocks);
|
|
95
|
+
}
|
|
96
|
+
this.lastCheckedBlockHeight = this.finalizedBlockNumber;
|
|
97
|
+
}
|
|
98
|
+
// remove any records less and equal than input finalized blockHeight
|
|
99
|
+
removeFinalized(blockHeight) {
|
|
100
|
+
this.unfinalizedBlocks = this.unfinalizedBlocks.filter(([height]) => height > blockHeight);
|
|
101
|
+
}
|
|
102
|
+
// find closest record from block heights
|
|
103
|
+
getClosestRecord(blockHeight) {
|
|
104
|
+
// Have the block in the best block, can be verified
|
|
105
|
+
const record = [...this.unfinalizedBlocks] // Copy so we can reverse
|
|
106
|
+
.reverse() // Reverse the list to find the largest block
|
|
107
|
+
.find(([bestBlockHeight]) => Number(bestBlockHeight) <= blockHeight);
|
|
108
|
+
if (record) {
|
|
109
|
+
const [bestBlockHeight, hash] = record;
|
|
110
|
+
return { blockHeight: Number(bestBlockHeight), hash };
|
|
111
|
+
}
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
// check unfinalized blocks for a fork, returns the header where a fork happened
|
|
115
|
+
async hasForked() {
|
|
116
|
+
const lastVerifiableBlock = this.getClosestRecord(this.finalizedBlockNumber);
|
|
117
|
+
// No unfinalized blocks
|
|
118
|
+
if (!lastVerifiableBlock) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
// Unfinalized blocks beyond finalized block
|
|
122
|
+
if (lastVerifiableBlock.blockHeight === this.finalizedBlockNumber) {
|
|
123
|
+
if (lastVerifiableBlock.hash !== this.finalizedHeader.hash) {
|
|
124
|
+
logger.warn(`Block fork found, enqueued un-finalized block at ${lastVerifiableBlock.blockHeight} with hash ${lastVerifiableBlock.hash}, actual hash is ${this.finalizedHeader.hash}`);
|
|
125
|
+
return this.finalizedHeader;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
// Unfinalized blocks below finalized block
|
|
130
|
+
let header = this.finalizedHeader;
|
|
131
|
+
/*
|
|
132
|
+
* Iterate back through parent hashes until we get the header with the matching height
|
|
133
|
+
* We use headers here rather than getBlockHash because of potential caching issues on the rpc
|
|
134
|
+
* If we're off by a large number of blocks we can optimise by getting the block hash directly
|
|
135
|
+
*/
|
|
136
|
+
if (header.number - lastVerifiableBlock.blockHeight >
|
|
137
|
+
UNFINALIZED_THRESHOLD) {
|
|
138
|
+
header = await this.api.getBlockByHeightOrHash(lastVerifiableBlock.blockHeight);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
while (lastVerifiableBlock.blockHeight !== header.number) {
|
|
142
|
+
header = await this.api.getBlockByHeightOrHash(header.parentHash);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (header.hash !== lastVerifiableBlock.hash) {
|
|
146
|
+
logger.warn(`Block fork found, enqueued un-finalized block at ${lastVerifiableBlock.blockHeight} with hash ${lastVerifiableBlock.hash}, actual hash is ${header.hash}`);
|
|
147
|
+
return header;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
async getLastCorrectFinalizedBlock(forkedHeader) {
|
|
153
|
+
const bestVerifiableBlocks = this.unfinalizedBlocks.filter(([bestBlockHeight]) => Number(bestBlockHeight) <= this.finalizedBlockNumber);
|
|
154
|
+
let checkingHeader = forkedHeader;
|
|
155
|
+
// Work backwards through the blocks until we find a matching hash
|
|
156
|
+
for (const [block, hash] of bestVerifiableBlocks.reverse()) {
|
|
157
|
+
if (hash === checkingHeader.hash || hash === checkingHeader.parentHash) {
|
|
158
|
+
return Number(block);
|
|
159
|
+
}
|
|
160
|
+
// Get the new parent
|
|
161
|
+
checkingHeader = await this.api.getBlockByHeightOrHash(checkingHeader.parentHash);
|
|
162
|
+
}
|
|
163
|
+
return this.lastCheckedBlockHeight;
|
|
164
|
+
}
|
|
165
|
+
saveUnfinalizedBlocks(unfinalizedBlocks) {
|
|
166
|
+
return this.storeCache.metadata.set(exports.METADATA_UNFINALIZED_BLOCKS_KEY, JSON.stringify(unfinalizedBlocks));
|
|
167
|
+
}
|
|
168
|
+
saveLastFinalizedVerifiedHeight(height) {
|
|
169
|
+
return this.storeCache.metadata.set(exports.METADATA_LAST_FINALIZED_PROCESSED_KEY, height);
|
|
170
|
+
}
|
|
171
|
+
resetUnfinalizedBlocks() {
|
|
172
|
+
this.storeCache.metadata.set(exports.METADATA_UNFINALIZED_BLOCKS_KEY, '[]');
|
|
173
|
+
this.unfinalizedBlocks = [];
|
|
174
|
+
}
|
|
175
|
+
resetLastFinalizedVerifiedHeight() {
|
|
176
|
+
return this.storeCache.metadata.set(exports.METADATA_LAST_FINALIZED_PROCESSED_KEY, null);
|
|
177
|
+
}
|
|
178
|
+
//string should be jsonb object
|
|
179
|
+
async getMetadataUnfinalizedBlocks() {
|
|
180
|
+
const val = await this.storeCache.metadata.find(exports.METADATA_UNFINALIZED_BLOCKS_KEY);
|
|
181
|
+
if (val) {
|
|
182
|
+
return JSON.parse(val);
|
|
183
|
+
}
|
|
184
|
+
return [];
|
|
185
|
+
}
|
|
186
|
+
async getLastFinalizedVerifiedHeight() {
|
|
187
|
+
return this.storeCache.metadata.find(exports.METADATA_LAST_FINALIZED_PROCESSED_KEY);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
UnfinalizedBlocksService = __decorate([
|
|
191
|
+
(0, common_1.Injectable)(),
|
|
192
|
+
__metadata("design:paramtypes", [node_core_1.ApiService,
|
|
193
|
+
node_core_1.NodeConfig,
|
|
194
|
+
node_core_1.StoreCacheService])
|
|
195
|
+
], UnfinalizedBlocksService);
|
|
196
|
+
exports.UnfinalizedBlocksService = UnfinalizedBlocksService;
|
|
197
|
+
//# sourceMappingURL=unfinalizedBlocks.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unfinalizedBlocks.service.js","sourceRoot":"","sources":["../../src/indexer/unfinalizedBlocks.service.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2020-2022 OnFinality Limited authors & contributors\n// SPDX-License-Identifier: Apache-2.0\n\nimport { Block } from '@ethersproject/abstract-provider';\nimport { Injectable } from '@nestjs/common';\nimport {\n ApiService,\n getLogger,\n NodeConfig,\n StoreCacheService,\n} from '@subql/node-core';\nimport { EthereumBlock } from '@subql/types-ethereum';\nimport { last } from 'lodash';\nimport { EthereumApi } from '../ethereum';\n\nconst logger = getLogger('UnfinalizedBlocks');\n\nexport const METADATA_UNFINALIZED_BLOCKS_KEY = 'unfinalizedBlocks';\nexport const METADATA_LAST_FINALIZED_PROCESSED_KEY =\n 'lastFinalizedVerifiedHeight';\n\nconst UNFINALIZED_THRESHOLD = 200;\n\ntype UnfinalizedBlocks = [blockHeight: number, blockHash: string][];\nexport interface IUnfinalizedBlocksService {\n processUnfinalizedBlocks(\n block: EthereumBlock | undefined,\n ): Promise<number | null>;\n}\n\n@Injectable()\nexport class UnfinalizedBlocksService implements IUnfinalizedBlocksService {\n private unfinalizedBlocks: UnfinalizedBlocks;\n private finalizedHeader: Block;\n private lastCheckedBlockHeight: number;\n\n constructor(\n private readonly apiService: ApiService,\n private readonly nodeConfig: NodeConfig,\n private readonly storeCache: StoreCacheService,\n ) {}\n\n async init(\n reindex: (targetHeight: number) => Promise<void>,\n ): Promise<number | undefined> {\n logger.info(\n `Unfinalized blocks is ${\n this.nodeConfig.unfinalizedBlocks ? 'enabled' : 'disabled'\n }`,\n );\n // unfinalized blocks\n this.unfinalizedBlocks = await this.getMetadataUnfinalizedBlocks();\n this.lastCheckedBlockHeight = await this.getLastFinalizedVerifiedHeight();\n this.finalizedHeader = await this.api.getBlockByHeightOrHash(\n await this.api.getFinalizedBlockHeight(),\n );\n\n if (!this.nodeConfig.unfinalizedBlocks && this.unfinalizedBlocks.length) {\n logger.info('Processing unfinalized blocks');\n // Validate any previously unfinalized blocks\n\n const rewindHeight = await this.processUnfinalizedBlocks(null);\n\n if (rewindHeight !== null) {\n logger.info(\n `Found un-finalized blocks from previous indexing but unverified, rolling back to last finalized block ${rewindHeight} `,\n );\n await reindex(rewindHeight);\n logger.info(`Successful rewind to block ${rewindHeight}!`);\n return rewindHeight;\n } else {\n this.resetUnfinalizedBlocks();\n this.resetLastFinalizedVerifiedHeight();\n }\n }\n }\n\n async processUnfinalizedBlocks(\n block: EthereumBlock | undefined,\n ): Promise<number | null> {\n if (block) {\n this.registerUnfinalizedBlock(block.number, block.hash);\n }\n\n const forkedHeader = await this.hasForked();\n if (!forkedHeader) {\n // Remove blocks that are now confirmed finalized\n this.deleteFinalizedBlock();\n } else {\n // Get the last unfinalized block that is now finalized\n return this.getLastCorrectFinalizedBlock(forkedHeader);\n }\n\n return null;\n }\n\n private get api(): EthereumApi {\n return this.apiService.api;\n }\n\n private get finalizedBlockNumber(): number {\n return this.finalizedHeader.number;\n }\n\n registerFinalizedBlock(header: Block): void {\n if (this.finalizedHeader && this.finalizedBlockNumber >= header.number) {\n return;\n }\n this.finalizedHeader = header;\n }\n\n private registerUnfinalizedBlock(blockNumber: number, hash: string): void {\n if (blockNumber <= this.finalizedBlockNumber) return;\n\n // Ensure order\n if (\n this.unfinalizedBlocks.length &&\n last(this.unfinalizedBlocks)[0] + 1 !== blockNumber\n ) {\n logger.error('Unfinalized block is not sequential');\n process.exit(1);\n }\n\n this.unfinalizedBlocks.push([blockNumber, hash]);\n this.saveUnfinalizedBlocks(this.unfinalizedBlocks);\n }\n\n private deleteFinalizedBlock(): void {\n if (\n this.lastCheckedBlockHeight !== undefined &&\n this.lastCheckedBlockHeight < this.finalizedBlockNumber\n ) {\n this.removeFinalized(this.finalizedBlockNumber);\n this.saveLastFinalizedVerifiedHeight(this.finalizedBlockNumber);\n this.saveUnfinalizedBlocks(this.unfinalizedBlocks);\n }\n this.lastCheckedBlockHeight = this.finalizedBlockNumber;\n }\n\n // remove any records less and equal than input finalized blockHeight\n private removeFinalized(blockHeight: number): void {\n this.unfinalizedBlocks = this.unfinalizedBlocks.filter(\n ([height]) => height > blockHeight,\n );\n }\n\n // find closest record from block heights\n private getClosestRecord(\n blockHeight: number,\n ): { blockHeight: number; hash: string } | undefined {\n // Have the block in the best block, can be verified\n const record = [...this.unfinalizedBlocks] // Copy so we can reverse\n .reverse() // Reverse the list to find the largest block\n .find(([bestBlockHeight]) => Number(bestBlockHeight) <= blockHeight);\n if (record) {\n const [bestBlockHeight, hash] = record;\n return { blockHeight: Number(bestBlockHeight), hash };\n }\n return undefined;\n }\n\n // check unfinalized blocks for a fork, returns the header where a fork happened\n private async hasForked(): Promise<Block | undefined> {\n const lastVerifiableBlock = this.getClosestRecord(\n this.finalizedBlockNumber,\n );\n\n // No unfinalized blocks\n if (!lastVerifiableBlock) {\n return;\n }\n\n // Unfinalized blocks beyond finalized block\n if (lastVerifiableBlock.blockHeight === this.finalizedBlockNumber) {\n if (lastVerifiableBlock.hash !== this.finalizedHeader.hash) {\n logger.warn(\n `Block fork found, enqueued un-finalized block at ${lastVerifiableBlock.blockHeight} with hash ${lastVerifiableBlock.hash}, actual hash is ${this.finalizedHeader.hash}`,\n );\n return this.finalizedHeader;\n }\n } else {\n // Unfinalized blocks below finalized block\n let header = this.finalizedHeader;\n /*\n * Iterate back through parent hashes until we get the header with the matching height\n * We use headers here rather than getBlockHash because of potential caching issues on the rpc\n * If we're off by a large number of blocks we can optimise by getting the block hash directly\n */\n if (\n header.number - lastVerifiableBlock.blockHeight >\n UNFINALIZED_THRESHOLD\n ) {\n header = await this.api.getBlockByHeightOrHash(\n lastVerifiableBlock.blockHeight,\n );\n } else {\n while (lastVerifiableBlock.blockHeight !== header.number) {\n header = await this.api.getBlockByHeightOrHash(header.parentHash);\n }\n }\n\n if (header.hash !== lastVerifiableBlock.hash) {\n logger.warn(\n `Block fork found, enqueued un-finalized block at ${lastVerifiableBlock.blockHeight} with hash ${lastVerifiableBlock.hash}, actual hash is ${header.hash}`,\n );\n return header;\n }\n }\n\n return;\n }\n\n private async getLastCorrectFinalizedBlock(\n forkedHeader: Block,\n ): Promise<number | undefined> {\n const bestVerifiableBlocks = this.unfinalizedBlocks.filter(\n ([bestBlockHeight]) =>\n Number(bestBlockHeight) <= this.finalizedBlockNumber,\n );\n\n let checkingHeader = forkedHeader;\n\n // Work backwards through the blocks until we find a matching hash\n for (const [block, hash] of bestVerifiableBlocks.reverse()) {\n if (hash === checkingHeader.hash || hash === checkingHeader.parentHash) {\n return Number(block);\n }\n\n // Get the new parent\n checkingHeader = await this.api.getBlockByHeightOrHash(\n checkingHeader.parentHash,\n );\n }\n\n return this.lastCheckedBlockHeight;\n }\n\n private saveUnfinalizedBlocks(unfinalizedBlocks: UnfinalizedBlocks): void {\n return this.storeCache.metadata.set(\n METADATA_UNFINALIZED_BLOCKS_KEY,\n JSON.stringify(unfinalizedBlocks),\n );\n }\n\n private saveLastFinalizedVerifiedHeight(height: number): void {\n return this.storeCache.metadata.set(\n METADATA_LAST_FINALIZED_PROCESSED_KEY,\n height,\n );\n }\n\n resetUnfinalizedBlocks(): void {\n this.storeCache.metadata.set(METADATA_UNFINALIZED_BLOCKS_KEY, '[]');\n this.unfinalizedBlocks = [];\n }\n\n resetLastFinalizedVerifiedHeight(): void {\n return this.storeCache.metadata.set(\n METADATA_LAST_FINALIZED_PROCESSED_KEY,\n null,\n );\n }\n\n //string should be jsonb object\n async getMetadataUnfinalizedBlocks(): Promise<UnfinalizedBlocks> {\n const val = await this.storeCache.metadata.find(\n METADATA_UNFINALIZED_BLOCKS_KEY,\n );\n if (val) {\n return JSON.parse(val) as UnfinalizedBlocks;\n }\n return [];\n }\n\n async getLastFinalizedVerifiedHeight(): Promise<number | undefined> {\n return this.storeCache.metadata.find(METADATA_LAST_FINALIZED_PROCESSED_KEY);\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright 2020-2022 OnFinality Limited authors & contributors
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
const node_core_1 = require("@subql/node-core");
|
|
6
|
+
const unfinalizedBlocks_service_1 = require("./unfinalizedBlocks.service");
|
|
7
|
+
/* Notes:
|
|
8
|
+
* Block hashes all have the format '0xabc' + block number
|
|
9
|
+
* If they are forked they will have an `f` at the end
|
|
10
|
+
*/
|
|
11
|
+
function mockApiService() {
|
|
12
|
+
const mockApi = {
|
|
13
|
+
getFinalizedBlockHeight: jest.fn(() => 110),
|
|
14
|
+
getRuntimeChain: jest.fn(() => 'ethereum'),
|
|
15
|
+
getBlockByHeightOrHash: (heightOrHash) => {
|
|
16
|
+
if (typeof heightOrHash === 'string') {
|
|
17
|
+
const height = Number(heightOrHash.toString().replace('0xabc', '').replace('f', ''));
|
|
18
|
+
return {
|
|
19
|
+
number: height,
|
|
20
|
+
hash: heightOrHash,
|
|
21
|
+
parentHash: `0xabc${height - 1}f`,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
const hash = `0xabc${heightOrHash}f`;
|
|
26
|
+
return {
|
|
27
|
+
number: heightOrHash,
|
|
28
|
+
hash: hash,
|
|
29
|
+
parentHash: `0xabc${heightOrHash - 1}f`,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
return {
|
|
35
|
+
api: mockApi,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function getMockMetadata() {
|
|
39
|
+
const data = {};
|
|
40
|
+
return {
|
|
41
|
+
upsert: ({ key, value }) => (data[key] = value),
|
|
42
|
+
findOne: ({ where: { key } }) => ({ value: data[key] }),
|
|
43
|
+
findByPk: (key) => data[key],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function mockStoreCache() {
|
|
47
|
+
return {
|
|
48
|
+
metadata: new node_core_1.CacheMetadataModel(getMockMetadata()),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function mockBlock(height, hash, parentHash) {
|
|
52
|
+
return {
|
|
53
|
+
number: height,
|
|
54
|
+
hash: hash,
|
|
55
|
+
parentHash: parentHash,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
describe('UnfinalizedBlocksService', () => {
|
|
59
|
+
let apiService;
|
|
60
|
+
let unfinalizedBlocksService;
|
|
61
|
+
beforeEach(async () => {
|
|
62
|
+
apiService = mockApiService();
|
|
63
|
+
unfinalizedBlocksService = new unfinalizedBlocks_service_1.UnfinalizedBlocksService(apiService, { unfinalizedBlocks: true }, mockStoreCache());
|
|
64
|
+
await unfinalizedBlocksService.init(() => Promise.resolve());
|
|
65
|
+
});
|
|
66
|
+
afterEach(() => {
|
|
67
|
+
unfinalizedBlocksService.unfinalizedBlocks = {};
|
|
68
|
+
});
|
|
69
|
+
it('can set finalized block', () => {
|
|
70
|
+
unfinalizedBlocksService.registerFinalizedBlock(apiService.api.getBlockByHeightOrHash(110));
|
|
71
|
+
expect(unfinalizedBlocksService.finalizedBlockNumber).toBe(110);
|
|
72
|
+
});
|
|
73
|
+
it('cant set a lower finalized block', () => {
|
|
74
|
+
unfinalizedBlocksService.registerFinalizedBlock(apiService.api.getBlockByHeightOrHash(110));
|
|
75
|
+
unfinalizedBlocksService.registerFinalizedBlock(apiService.api.getBlockByHeightOrHash(99));
|
|
76
|
+
expect(unfinalizedBlocksService.finalizedBlockNumber).toBe(110);
|
|
77
|
+
});
|
|
78
|
+
it('keeps track of unfinalized blocks', async () => {
|
|
79
|
+
unfinalizedBlocksService.registerFinalizedBlock(apiService.api.getBlockByHeightOrHash(110));
|
|
80
|
+
await unfinalizedBlocksService.processUnfinalizedBlocks(mockBlock(111, '0xabc111'));
|
|
81
|
+
await unfinalizedBlocksService.processUnfinalizedBlocks(mockBlock(112, '0xabc112'));
|
|
82
|
+
expect(unfinalizedBlocksService.unfinalizedBlocks).toEqual([
|
|
83
|
+
[111, '0xabc111'],
|
|
84
|
+
[112, '0xabc112'],
|
|
85
|
+
]);
|
|
86
|
+
});
|
|
87
|
+
it('doesnt keep track of finalized blocks', async () => {
|
|
88
|
+
unfinalizedBlocksService.registerFinalizedBlock(apiService.api.getBlockByHeightOrHash(120));
|
|
89
|
+
await unfinalizedBlocksService.processUnfinalizedBlocks(mockBlock(111, '0xabc111'));
|
|
90
|
+
await unfinalizedBlocksService.processUnfinalizedBlocks(mockBlock(112, '0xabc112'));
|
|
91
|
+
expect(unfinalizedBlocksService.unfinalizedBlocks).toEqual([]);
|
|
92
|
+
});
|
|
93
|
+
it('can process unfinalized blocks', async () => {
|
|
94
|
+
unfinalizedBlocksService.registerFinalizedBlock(apiService.api.getBlockByHeightOrHash(110));
|
|
95
|
+
await unfinalizedBlocksService.processUnfinalizedBlocks(mockBlock(111, '0xabc111'));
|
|
96
|
+
await unfinalizedBlocksService.processUnfinalizedBlocks(mockBlock(112, '0xabc112'));
|
|
97
|
+
unfinalizedBlocksService.registerFinalizedBlock(mockBlock(112, '0xabc112', '0xabc111'));
|
|
98
|
+
await unfinalizedBlocksService.processUnfinalizedBlocks(mockBlock(113, '0xabc113'));
|
|
99
|
+
expect(unfinalizedBlocksService.unfinalizedBlocks).toEqual([
|
|
100
|
+
[113, '0xabc113'],
|
|
101
|
+
]);
|
|
102
|
+
});
|
|
103
|
+
it('can handle a fork and rewind to the last finalized height', async () => {
|
|
104
|
+
unfinalizedBlocksService.registerFinalizedBlock(apiService.api.getBlockByHeightOrHash(110));
|
|
105
|
+
await unfinalizedBlocksService.processUnfinalizedBlocks(mockBlock(111, '0xabc111'));
|
|
106
|
+
await unfinalizedBlocksService.processUnfinalizedBlocks(mockBlock(112, '0xabc112'));
|
|
107
|
+
// Forked block
|
|
108
|
+
unfinalizedBlocksService.registerFinalizedBlock(mockBlock(112, '0xabc112f', '0xabc111'));
|
|
109
|
+
const res = await unfinalizedBlocksService.processUnfinalizedBlocks(mockBlock(113, '0xabc113'));
|
|
110
|
+
// Last valid block
|
|
111
|
+
expect(res).toBe(111);
|
|
112
|
+
// After this the call stack is something like:
|
|
113
|
+
// indexerManager -> blockDispatcher -> project -> project -> reindex -> blockDispatcher.resetUnfinalizedBlocks
|
|
114
|
+
unfinalizedBlocksService.resetUnfinalizedBlocks();
|
|
115
|
+
expect(unfinalizedBlocksService.unfinalizedBlocks).toEqual([]);
|
|
116
|
+
});
|
|
117
|
+
it('can handle a fork when some unfinalized blocks are invalid', async () => {
|
|
118
|
+
unfinalizedBlocksService.registerFinalizedBlock(apiService.api.getBlockByHeightOrHash(110));
|
|
119
|
+
await unfinalizedBlocksService.processUnfinalizedBlocks(mockBlock(111, '0xabc111'));
|
|
120
|
+
await unfinalizedBlocksService.processUnfinalizedBlocks(mockBlock(112, '0xabc112'));
|
|
121
|
+
await unfinalizedBlocksService.processUnfinalizedBlocks(mockBlock(113, '0xabc113'));
|
|
122
|
+
await unfinalizedBlocksService.processUnfinalizedBlocks(mockBlock(114, '0xabc114'));
|
|
123
|
+
await unfinalizedBlocksService.processUnfinalizedBlocks(mockBlock(115, '0xabc115'));
|
|
124
|
+
await unfinalizedBlocksService.processUnfinalizedBlocks(mockBlock(116, '0xabc116'));
|
|
125
|
+
// Forked block
|
|
126
|
+
unfinalizedBlocksService.registerFinalizedBlock(mockBlock(113, '0xabc113f', '0xabc112'));
|
|
127
|
+
const res = await unfinalizedBlocksService.processUnfinalizedBlocks(mockBlock(117, '0xabc117'));
|
|
128
|
+
// Last valid block
|
|
129
|
+
expect(res).toBe(112);
|
|
130
|
+
});
|
|
131
|
+
it('can handle a fork when all unfinalized blocks are invalid', async () => {
|
|
132
|
+
unfinalizedBlocksService.registerFinalizedBlock(apiService.api.getBlockByHeightOrHash(110));
|
|
133
|
+
await unfinalizedBlocksService.processUnfinalizedBlocks(mockBlock(111, '0xabc111'));
|
|
134
|
+
await unfinalizedBlocksService.processUnfinalizedBlocks(mockBlock(112, '0xabc112'));
|
|
135
|
+
// Forked block
|
|
136
|
+
unfinalizedBlocksService.registerFinalizedBlock(mockBlock(111, '0xabc111f', '0xabc110'));
|
|
137
|
+
const res = await unfinalizedBlocksService.processUnfinalizedBlocks(mockBlock(113, '0xabc113'));
|
|
138
|
+
// Last valid block
|
|
139
|
+
expect(res).toBe(110);
|
|
140
|
+
});
|
|
141
|
+
it('can handle a fork and when unfinalized blocks < finalized head', async () => {
|
|
142
|
+
unfinalizedBlocksService.registerFinalizedBlock(apiService.api.getBlockByHeightOrHash(110));
|
|
143
|
+
await unfinalizedBlocksService.processUnfinalizedBlocks(mockBlock(111, '0xabc111'));
|
|
144
|
+
await unfinalizedBlocksService.processUnfinalizedBlocks(mockBlock(112, '0xabc112'));
|
|
145
|
+
// Forked block
|
|
146
|
+
unfinalizedBlocksService.registerFinalizedBlock(mockBlock(120, '0xabc120f', '0xabc119f'));
|
|
147
|
+
const res = await unfinalizedBlocksService.processUnfinalizedBlocks(mockBlock(113, '0xabc113'));
|
|
148
|
+
// Last valid block
|
|
149
|
+
expect(res).toBe(110);
|
|
150
|
+
});
|
|
151
|
+
it('can handle a fork and when unfinalized blocks < finalized head 2', async () => {
|
|
152
|
+
unfinalizedBlocksService.registerFinalizedBlock(apiService.api.getBlockByHeightOrHash(110));
|
|
153
|
+
unfinalizedBlocksService.lastCheckedBlockHeight = 110;
|
|
154
|
+
await unfinalizedBlocksService.registerUnfinalizedBlock(111, '0xabc111');
|
|
155
|
+
await unfinalizedBlocksService.registerUnfinalizedBlock(112, '0xabc112');
|
|
156
|
+
// Forked block
|
|
157
|
+
unfinalizedBlocksService.registerFinalizedBlock(mockBlock(120, '0xabc120f', '0xabc119f'));
|
|
158
|
+
const res = await unfinalizedBlocksService.processUnfinalizedBlocks(mockBlock(113, '0xabc113'));
|
|
159
|
+
// Last valid block
|
|
160
|
+
expect(res).toBe(110);
|
|
161
|
+
});
|
|
162
|
+
it('can handle a fork and when unfinalized blocks < finalized head with a large difference', async () => {
|
|
163
|
+
unfinalizedBlocksService.registerFinalizedBlock(apiService.api.getBlockByHeightOrHash(110));
|
|
164
|
+
await unfinalizedBlocksService.processUnfinalizedBlocks(mockBlock(111, '0xabc111'));
|
|
165
|
+
await unfinalizedBlocksService.processUnfinalizedBlocks(mockBlock(112, '0xabc112'));
|
|
166
|
+
// Forked block
|
|
167
|
+
unfinalizedBlocksService.registerFinalizedBlock(mockBlock(1200, '0xabc1200f', '0xabc1199f'));
|
|
168
|
+
const res = await unfinalizedBlocksService.processUnfinalizedBlocks(mockBlock(113, '0xabc113'));
|
|
169
|
+
// Last valid block
|
|
170
|
+
expect(res).toBe(110);
|
|
171
|
+
});
|
|
172
|
+
it('can rewind any unfinalized blocks when restarted and unfinalized blocks is disabled', async () => {
|
|
173
|
+
const metadata = getMockMetadata();
|
|
174
|
+
metadata.upsert({
|
|
175
|
+
key: unfinalizedBlocks_service_1.METADATA_UNFINALIZED_BLOCKS_KEY,
|
|
176
|
+
value: JSON.stringify([
|
|
177
|
+
[90, '0xabcd'],
|
|
178
|
+
[91, '0xabc91'],
|
|
179
|
+
[92, '0xabc92'],
|
|
180
|
+
]),
|
|
181
|
+
});
|
|
182
|
+
metadata.upsert({
|
|
183
|
+
key: unfinalizedBlocks_service_1.METADATA_LAST_FINALIZED_PROCESSED_KEY,
|
|
184
|
+
value: 90,
|
|
185
|
+
});
|
|
186
|
+
const unfinalizedBlocksService2 = new unfinalizedBlocks_service_1.UnfinalizedBlocksService(apiService, { unfinalizedBlocks: false }, {
|
|
187
|
+
transaction: () => Promise.resolve({ commit: () => undefined }),
|
|
188
|
+
});
|
|
189
|
+
const reindex = jest.fn().mockReturnValue(Promise.resolve());
|
|
190
|
+
await unfinalizedBlocksService2.init(reindex);
|
|
191
|
+
expect(reindex).toBeCalledWith(90);
|
|
192
|
+
expect(unfinalizedBlocksService2.lastCheckedBlockHeight).toBe(90);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
//# sourceMappingURL=unfinalizedBlocks.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unfinalizedBlocks.spec.js","sourceRoot":"","sources":["../../src/indexer/unfinalizedBlocks.spec.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2020-2022 OnFinality Limited authors & contributors\n// SPDX-License-Identifier: Apache-2.0\n\nimport { Block } from '@ethersproject/abstract-provider';\nimport {\n ApiService,\n CacheMetadataModel,\n StoreCacheService,\n} from '@subql/node-core';\nimport { EthereumBlock } from '@subql/types-ethereum';\nimport {\n METADATA_LAST_FINALIZED_PROCESSED_KEY,\n METADATA_UNFINALIZED_BLOCKS_KEY,\n UnfinalizedBlocksService,\n} from './unfinalizedBlocks.service';\n\n/* Notes:\n * Block hashes all have the format '0xabc' + block number\n * If they are forked they will have an `f` at the end\n */\n\nfunction mockApiService(): ApiService {\n const mockApi = {\n getFinalizedBlockHeight: jest.fn(() => 110),\n getRuntimeChain: jest.fn(() => 'ethereum'),\n getBlockByHeightOrHash: (heightOrHash: string | number): Block => {\n if (typeof heightOrHash === 'string') {\n const height = Number(\n heightOrHash.toString().replace('0xabc', '').replace('f', ''),\n );\n\n return {\n number: height,\n hash: heightOrHash,\n parentHash: `0xabc${height - 1}f`,\n } as unknown as Block;\n } else {\n const hash = `0xabc${heightOrHash}f`;\n return {\n number: heightOrHash,\n hash: hash,\n parentHash: `0xabc${heightOrHash - 1}f`,\n } as unknown as Block;\n }\n },\n };\n return {\n api: mockApi,\n } as any;\n}\n\nfunction getMockMetadata(): any {\n const data: Record<string, any> = {};\n return {\n upsert: ({ key, value }) => (data[key] = value),\n findOne: ({ where: { key } }) => ({ value: data[key] }),\n findByPk: (key: string) => data[key],\n } as any;\n}\n\nfunction mockStoreCache(): StoreCacheService {\n return {\n metadata: new CacheMetadataModel(getMockMetadata()),\n } as StoreCacheService;\n}\n\nfunction mockBlock(\n height: number,\n hash: string,\n parentHash?: string,\n): EthereumBlock {\n return {\n number: height,\n hash: hash,\n parentHash: parentHash,\n } as unknown as EthereumBlock;\n}\n\ndescribe('UnfinalizedBlocksService', () => {\n let apiService: ApiService;\n let unfinalizedBlocksService: UnfinalizedBlocksService;\n\n beforeEach(async () => {\n apiService = mockApiService();\n unfinalizedBlocksService = new UnfinalizedBlocksService(\n apiService,\n { unfinalizedBlocks: true } as any,\n mockStoreCache(),\n );\n\n await unfinalizedBlocksService.init(() => Promise.resolve());\n });\n\n afterEach(() => {\n (unfinalizedBlocksService as unknown as any).unfinalizedBlocks = {};\n });\n\n it('can set finalized block', () => {\n unfinalizedBlocksService.registerFinalizedBlock(\n apiService.api.getBlockByHeightOrHash(110),\n );\n\n expect((unfinalizedBlocksService as any).finalizedBlockNumber).toBe(110);\n });\n\n it('cant set a lower finalized block', () => {\n unfinalizedBlocksService.registerFinalizedBlock(\n apiService.api.getBlockByHeightOrHash(110),\n );\n\n unfinalizedBlocksService.registerFinalizedBlock(\n apiService.api.getBlockByHeightOrHash(99),\n );\n\n expect((unfinalizedBlocksService as any).finalizedBlockNumber).toBe(110);\n });\n\n it('keeps track of unfinalized blocks', async () => {\n unfinalizedBlocksService.registerFinalizedBlock(\n apiService.api.getBlockByHeightOrHash(110),\n );\n\n await unfinalizedBlocksService.processUnfinalizedBlocks(\n mockBlock(111, '0xabc111'),\n );\n await unfinalizedBlocksService.processUnfinalizedBlocks(\n mockBlock(112, '0xabc112'),\n );\n\n expect((unfinalizedBlocksService as any).unfinalizedBlocks).toEqual([\n [111, '0xabc111'],\n [112, '0xabc112'],\n ]);\n });\n\n it('doesnt keep track of finalized blocks', async () => {\n unfinalizedBlocksService.registerFinalizedBlock(\n apiService.api.getBlockByHeightOrHash(120),\n );\n\n await unfinalizedBlocksService.processUnfinalizedBlocks(\n mockBlock(111, '0xabc111'),\n );\n await unfinalizedBlocksService.processUnfinalizedBlocks(\n mockBlock(112, '0xabc112'),\n );\n\n expect((unfinalizedBlocksService as any).unfinalizedBlocks).toEqual([]);\n });\n\n it('can process unfinalized blocks', async () => {\n unfinalizedBlocksService.registerFinalizedBlock(\n apiService.api.getBlockByHeightOrHash(110),\n );\n\n await unfinalizedBlocksService.processUnfinalizedBlocks(\n mockBlock(111, '0xabc111'),\n );\n await unfinalizedBlocksService.processUnfinalizedBlocks(\n mockBlock(112, '0xabc112'),\n );\n\n unfinalizedBlocksService.registerFinalizedBlock(\n mockBlock(112, '0xabc112', '0xabc111') as unknown as Block,\n );\n\n await unfinalizedBlocksService.processUnfinalizedBlocks(\n mockBlock(113, '0xabc113'),\n );\n\n expect((unfinalizedBlocksService as any).unfinalizedBlocks).toEqual([\n [113, '0xabc113'],\n ]);\n });\n\n it('can handle a fork and rewind to the last finalized height', async () => {\n unfinalizedBlocksService.registerFinalizedBlock(\n apiService.api.getBlockByHeightOrHash(110),\n );\n\n await unfinalizedBlocksService.processUnfinalizedBlocks(\n mockBlock(111, '0xabc111'),\n );\n await unfinalizedBlocksService.processUnfinalizedBlocks(\n mockBlock(112, '0xabc112'),\n );\n\n // Forked block\n unfinalizedBlocksService.registerFinalizedBlock(\n mockBlock(112, '0xabc112f', '0xabc111') as unknown as Block,\n );\n\n const res = await unfinalizedBlocksService.processUnfinalizedBlocks(\n mockBlock(113, '0xabc113'),\n );\n\n // Last valid block\n expect(res).toBe(111);\n\n // After this the call stack is something like:\n // indexerManager -> blockDispatcher -> project -> project -> reindex -> blockDispatcher.resetUnfinalizedBlocks\n unfinalizedBlocksService.resetUnfinalizedBlocks();\n\n expect((unfinalizedBlocksService as any).unfinalizedBlocks).toEqual([]);\n });\n\n it('can handle a fork when some unfinalized blocks are invalid', async () => {\n unfinalizedBlocksService.registerFinalizedBlock(\n apiService.api.getBlockByHeightOrHash(110),\n );\n\n await unfinalizedBlocksService.processUnfinalizedBlocks(\n mockBlock(111, '0xabc111'),\n );\n await unfinalizedBlocksService.processUnfinalizedBlocks(\n mockBlock(112, '0xabc112'),\n );\n await unfinalizedBlocksService.processUnfinalizedBlocks(\n mockBlock(113, '0xabc113'),\n );\n await unfinalizedBlocksService.processUnfinalizedBlocks(\n mockBlock(114, '0xabc114'),\n );\n await unfinalizedBlocksService.processUnfinalizedBlocks(\n mockBlock(115, '0xabc115'),\n );\n await unfinalizedBlocksService.processUnfinalizedBlocks(\n mockBlock(116, '0xabc116'),\n );\n\n // Forked block\n unfinalizedBlocksService.registerFinalizedBlock(\n mockBlock(113, '0xabc113f', '0xabc112') as unknown as Block,\n );\n\n const res = await unfinalizedBlocksService.processUnfinalizedBlocks(\n mockBlock(117, '0xabc117'),\n );\n\n // Last valid block\n expect(res).toBe(112);\n });\n\n it('can handle a fork when all unfinalized blocks are invalid', async () => {\n unfinalizedBlocksService.registerFinalizedBlock(\n apiService.api.getBlockByHeightOrHash(110),\n );\n\n await unfinalizedBlocksService.processUnfinalizedBlocks(\n mockBlock(111, '0xabc111'),\n );\n await unfinalizedBlocksService.processUnfinalizedBlocks(\n mockBlock(112, '0xabc112'),\n );\n\n // Forked block\n unfinalizedBlocksService.registerFinalizedBlock(\n mockBlock(111, '0xabc111f', '0xabc110') as unknown as Block,\n );\n\n const res = await unfinalizedBlocksService.processUnfinalizedBlocks(\n mockBlock(113, '0xabc113'),\n );\n\n // Last valid block\n expect(res).toBe(110);\n });\n\n it('can handle a fork and when unfinalized blocks < finalized head', async () => {\n unfinalizedBlocksService.registerFinalizedBlock(\n apiService.api.getBlockByHeightOrHash(110),\n );\n\n await unfinalizedBlocksService.processUnfinalizedBlocks(\n mockBlock(111, '0xabc111'),\n );\n await unfinalizedBlocksService.processUnfinalizedBlocks(\n mockBlock(112, '0xabc112'),\n );\n\n // Forked block\n unfinalizedBlocksService.registerFinalizedBlock(\n mockBlock(120, '0xabc120f', '0xabc119f') as unknown as Block,\n );\n\n const res = await unfinalizedBlocksService.processUnfinalizedBlocks(\n mockBlock(113, '0xabc113'),\n );\n\n // Last valid block\n expect(res).toBe(110);\n });\n\n it('can handle a fork and when unfinalized blocks < finalized head 2', async () => {\n unfinalizedBlocksService.registerFinalizedBlock(\n apiService.api.getBlockByHeightOrHash(110),\n );\n\n (unfinalizedBlocksService as any).lastCheckedBlockHeight = 110;\n\n await (unfinalizedBlocksService as any).registerUnfinalizedBlock(\n 111,\n '0xabc111',\n );\n await (unfinalizedBlocksService as any).registerUnfinalizedBlock(\n 112,\n '0xabc112',\n );\n\n // Forked block\n unfinalizedBlocksService.registerFinalizedBlock(\n mockBlock(120, '0xabc120f', '0xabc119f') as unknown as Block,\n );\n\n const res = await unfinalizedBlocksService.processUnfinalizedBlocks(\n mockBlock(113, '0xabc113'),\n );\n\n // Last valid block\n expect(res).toBe(110);\n });\n\n it('can handle a fork and when unfinalized blocks < finalized head with a large difference', async () => {\n unfinalizedBlocksService.registerFinalizedBlock(\n apiService.api.getBlockByHeightOrHash(110),\n );\n\n await unfinalizedBlocksService.processUnfinalizedBlocks(\n mockBlock(111, '0xabc111'),\n );\n await unfinalizedBlocksService.processUnfinalizedBlocks(\n mockBlock(112, '0xabc112'),\n );\n\n // Forked block\n unfinalizedBlocksService.registerFinalizedBlock(\n mockBlock(1200, '0xabc1200f', '0xabc1199f') as unknown as Block,\n );\n\n const res = await unfinalizedBlocksService.processUnfinalizedBlocks(\n mockBlock(113, '0xabc113'),\n );\n\n // Last valid block\n expect(res).toBe(110);\n });\n\n it('can rewind any unfinalized blocks when restarted and unfinalized blocks is disabled', async () => {\n const metadata = getMockMetadata();\n\n metadata.upsert({\n key: METADATA_UNFINALIZED_BLOCKS_KEY,\n value: JSON.stringify([\n [90, '0xabcd'],\n [91, '0xabc91'],\n [92, '0xabc92'],\n ]),\n });\n\n metadata.upsert({\n key: METADATA_LAST_FINALIZED_PROCESSED_KEY,\n value: 90,\n });\n\n const unfinalizedBlocksService2 = new UnfinalizedBlocksService(\n apiService,\n { unfinalizedBlocks: false } as any,\n {\n transaction: () => Promise.resolve({ commit: () => undefined }),\n } as any,\n );\n\n const reindex = jest.fn().mockReturnValue(Promise.resolve());\n\n await unfinalizedBlocksService2.init(reindex);\n\n expect(reindex).toBeCalledWith(90);\n expect((unfinalizedBlocksService2 as any).lastCheckedBlockHeight).toBe(90);\n });\n});\n"]}
|
|
@@ -5,10 +5,26 @@ declare function processBlock(height: number): Promise<ProcessBlockResponse>;
|
|
|
5
5
|
declare function numFetchedBlocks(): Promise<number>;
|
|
6
6
|
declare function numFetchingBlocks(): Promise<number>;
|
|
7
7
|
declare function getStatus(): Promise<WorkerStatusResponse>;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
declare function getMemoryLeft(): Promise<number>;
|
|
9
|
+
declare function waitForWorkerBatchSize(heapSizeInBytes: number): Promise<void>;
|
|
10
|
+
type InitWorker = typeof initWorker;
|
|
11
|
+
type FetchBlock = typeof fetchBlock;
|
|
12
|
+
type ProcessBlock = typeof processBlock;
|
|
13
|
+
type NumFetchedBlocks = typeof numFetchedBlocks;
|
|
14
|
+
type NumFetchingBlocks = typeof numFetchingBlocks;
|
|
15
|
+
type GetWorkerStatus = typeof getStatus;
|
|
16
|
+
type GetMemoryLeft = typeof getMemoryLeft;
|
|
17
|
+
type WaitForWorkerBatchSize = typeof waitForWorkerBatchSize;
|
|
18
|
+
export type IIndexerWorker = {
|
|
19
|
+
processBlock: ProcessBlock;
|
|
20
|
+
fetchBlock: FetchBlock;
|
|
21
|
+
numFetchedBlocks: NumFetchedBlocks;
|
|
22
|
+
numFetchingBlocks: NumFetchingBlocks;
|
|
23
|
+
getStatus: GetWorkerStatus;
|
|
24
|
+
getMemoryLeft: GetMemoryLeft;
|
|
25
|
+
waitForWorkerBatchSize: WaitForWorkerBatchSize;
|
|
26
|
+
};
|
|
27
|
+
export type IInitIndexerWorker = IIndexerWorker & {
|
|
28
|
+
initWorker: InitWorker;
|
|
29
|
+
};
|
|
14
30
|
export {};
|
|
@@ -15,11 +15,13 @@ const { argv } = yargs_1.yargsOptions;
|
|
|
15
15
|
(0, logger_1.initLogger)(argv.debug, argv.outputFmt, argv.logLevel);
|
|
16
16
|
const assert_1 = __importDefault(require("assert"));
|
|
17
17
|
const node_worker_threads_1 = require("node:worker_threads");
|
|
18
|
+
const v8_1 = require("v8");
|
|
18
19
|
const core_1 = require("@nestjs/core");
|
|
19
20
|
const node_core_1 = require("@subql/node-core");
|
|
20
21
|
const indexer_manager_1 = require("../indexer.manager");
|
|
21
22
|
const worker_module_1 = require("./worker.module");
|
|
22
23
|
const worker_service_1 = require("./worker.service");
|
|
24
|
+
const worker_unfinalizedBlocks_service_1 = require("./worker.unfinalizedBlocks.service");
|
|
23
25
|
let app;
|
|
24
26
|
let workerService;
|
|
25
27
|
const logger = (0, node_core_1.getLogger)(`worker #${node_worker_threads_1.threadId}`);
|
|
@@ -30,7 +32,7 @@ async function initWorker() {
|
|
|
30
32
|
return;
|
|
31
33
|
}
|
|
32
34
|
app = await core_1.NestFactory.create(worker_module_1.WorkerModule, {
|
|
33
|
-
logger: new node_core_1.NestLogger(),
|
|
35
|
+
logger: new node_core_1.NestLogger(), // TIP: If the worker is crashing comment out this line for better logging
|
|
34
36
|
});
|
|
35
37
|
await app.init();
|
|
36
38
|
const indexerManager = app.get(indexer_manager_1.IndexerManager);
|
|
@@ -69,15 +71,26 @@ async function getStatus() {
|
|
|
69
71
|
isIndexing: workerService.isIndexing,
|
|
70
72
|
};
|
|
71
73
|
}
|
|
74
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
75
|
+
async function getMemoryLeft() {
|
|
76
|
+
const totalHeap = (0, v8_1.getHeapStatistics)().heap_size_limit;
|
|
77
|
+
const heapUsed = process.memoryUsage().heapUsed;
|
|
78
|
+
return totalHeap - heapUsed;
|
|
79
|
+
}
|
|
80
|
+
async function waitForWorkerBatchSize(heapSizeInBytes) {
|
|
81
|
+
await (0, node_core_1.waitForBatchSize)(heapSizeInBytes);
|
|
82
|
+
}
|
|
72
83
|
// Register these functions to be exposed to worker host
|
|
73
|
-
(
|
|
84
|
+
global.host = node_core_1.WorkerHost.create([...node_core_1.hostStoreKeys, ...node_core_1.hostDynamicDsKeys, ...worker_unfinalizedBlocks_service_1.hostUnfinalizedBlocksKeys], {
|
|
74
85
|
initWorker,
|
|
75
86
|
fetchBlock,
|
|
76
87
|
processBlock,
|
|
77
88
|
numFetchedBlocks,
|
|
78
89
|
numFetchingBlocks,
|
|
79
90
|
getStatus,
|
|
80
|
-
|
|
91
|
+
getMemoryLeft,
|
|
92
|
+
waitForWorkerBatchSize,
|
|
93
|
+
}, logger);
|
|
81
94
|
process.on('uncaughtException', (e) => {
|
|
82
95
|
logger.error(e, 'Uncaught Exception');
|
|
83
96
|
throw e;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../../../src/indexer/worker/worker.ts"],"names":[],"mappings":";AAAA,gEAAgE;AAChE,sCAAsC;;;;;AAEtC,iEAAiE;AACjE,kDAAkD;AAClD,wCAAwC;AACxC,oDAAqD;AACrD,wCAAwC;AACxC,uCAA2C;AAE3C,MAAM,EAAE,IAAI,EAAE,GAAG,oBAAY,CAAC;AAE9B,IAAA,mBAAU,EACR,IAAI,CAAC,KAAK,EACV,IAAI,CAAC,SAA+B,EACpC,IAAI,CAAC,QAA8B,CACpC,CAAC;AAEF,oDAA4B;AAC5B,6DAA+C;
|
|
1
|
+
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../../../src/indexer/worker/worker.ts"],"names":[],"mappings":";AAAA,gEAAgE;AAChE,sCAAsC;;;;;AAEtC,iEAAiE;AACjE,kDAAkD;AAClD,wCAAwC;AACxC,oDAAqD;AACrD,wCAAwC;AACxC,uCAA2C;AAE3C,MAAM,EAAE,IAAI,EAAE,GAAG,oBAAY,CAAC;AAE9B,IAAA,mBAAU,EACR,IAAI,CAAC,KAAK,EACV,IAAI,CAAC,SAA+B,EACpC,IAAI,CAAC,QAA8B,CACpC,CAAC;AAEF,oDAA4B;AAC5B,6DAA+C;AAC/C,2BAAuC;AAEvC,uCAA2C;AAC3C,gDAS0B;AAE1B,wDAAoD;AACpD,mDAA+C;AAC/C,qDAK0B;AAC1B,yFAG4C;AAC5C,IAAI,GAAqB,CAAC;AAC1B,IAAI,aAA4B,CAAC;AAEjC,MAAM,MAAM,GAAG,IAAA,qBAAS,EAAC,WAAW,8BAAQ,EAAE,CAAC,CAAC;AAEhD,KAAK,UAAU,UAAU;IACvB,IAAI;QACF,IAAI,GAAG,EAAE;YACP,MAAM,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;YAC1C,OAAO;SACR;QAED,GAAG,GAAG,MAAM,kBAAW,CAAC,MAAM,CAAC,4BAAY,EAAE;YAC3C,MAAM,EAAE,IAAI,sBAAU,EAAE,EAAE,0EAA0E;SACrG,CAAC,CAAC;QAEH,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAEjB,MAAM,cAAc,GAAG,GAAG,CAAC,GAAG,CAAC,gCAAc,CAAC,CAAC;QAC/C,uGAAuG;QACvG,MAAM,cAAc,CAAC,KAAK,EAAE,CAAC;QAE7B,aAAa,GAAG,GAAG,CAAC,GAAG,CAAC,8BAAa,CAAC,CAAC;KACxC;IAAC,OAAO,CAAC,EAAE;QACV,OAAO,CAAC,GAAG,CAAC,wBAAwB,EAAE,CAAC,CAAC,CAAC;QACzC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,wBAAwB,CAAC,CAAC;QAC1C,MAAM,CAAC,CAAC;KACT;AACH,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,MAAc;IACtC,IAAA,gBAAM,EAAC,aAAa,EAAE,iBAAiB,CAAC,CAAC;IACzC,OAAO,aAAa,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;AAC1C,CAAC;AAED,KAAK,UAAU,YAAY,CAAC,MAAc;IACxC,IAAA,gBAAM,EAAC,aAAa,EAAE,iBAAiB,CAAC,CAAC;IAEzC,OAAO,aAAa,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;AAC5C,CAAC;AAED,4DAA4D;AAC5D,KAAK,UAAU,gBAAgB;IAC7B,OAAO,aAAa,CAAC,gBAAgB,CAAC;AACxC,CAAC;AAED,4DAA4D;AAC5D,KAAK,UAAU,iBAAiB;IAC9B,OAAO,aAAa,CAAC,iBAAiB,CAAC;AACzC,CAAC;AAED,4DAA4D;AAC5D,KAAK,UAAU,SAAS;IACtB,OAAO;QACL,QAAQ,EAAR,8BAAQ;QACR,aAAa,EAAE,aAAa,CAAC,gBAAgB;QAC7C,aAAa,EAAE,aAAa,CAAC,iBAAiB;QAC9C,UAAU,EAAE,aAAa,CAAC,UAAU;KACrC,CAAC;AACJ,CAAC;AAED,4DAA4D;AAC5D,KAAK,UAAU,aAAa;IAC1B,MAAM,SAAS,GAAG,IAAA,sBAAiB,GAAE,CAAC,eAAe,CAAC;IACtD,MAAM,QAAQ,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC;IAEhD,OAAO,SAAS,GAAG,QAAQ,CAAC;AAC9B,CAAC;AAED,KAAK,UAAU,sBAAsB,CAAC,eAAuB;IAC3D,MAAM,IAAA,4BAAgB,EAAC,eAAe,CAAC,CAAC;AAC1C,CAAC;AAED,wDAAwD;AACvD,MAAc,CAAC,IAAI,GAAG,sBAAU,CAAC,MAAM,CAItC,CAAC,GAAG,yBAAa,EAAE,GAAG,6BAAiB,EAAE,GAAG,4DAAyB,CAAC,EACtE;IACE,UAAU;IACV,UAAU;IACV,YAAY;IACZ,gBAAgB;IAChB,iBAAiB;IACjB,SAAS;IACT,aAAa;IACb,sBAAsB;CACvB,EACD,MAAM,CACP,CAAC;AA0BF,OAAO,CAAC,EAAE,CAAC,mBAAmB,EAAE,CAAC,CAAC,EAAE,EAAE;IACpC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,oBAAoB,CAAC,CAAC;IACtC,MAAM,CAAC,CAAC;AACV,CAAC,CAAC,CAAC","sourcesContent":["// Copyright 2020-2021 OnFinality Limited authors & contributors\n// SPDX-License-Identifier: Apache-2.0\n\n// initlogger and yargs must be imported before all other imports\n// making sure logger is defined before its called\n// eslint-disable-next-line import/order\nimport { initLogger } from '@subql/node-core/logger';\n// eslint-disable-next-line import/order\nimport { yargsOptions } from '../../yargs';\n\nconst { argv } = yargsOptions;\n\ninitLogger(\n argv.debug,\n argv.outputFmt as 'json' | 'colored',\n argv.logLevel as string | undefined,\n);\n\nimport assert from 'assert';\nimport { threadId } from 'node:worker_threads';\nimport { getHeapStatistics } from 'v8';\nimport { INestApplication } from '@nestjs/common';\nimport { NestFactory } from '@nestjs/core';\nimport {\n waitForBatchSize,\n WorkerHost,\n getLogger,\n NestLogger,\n hostStoreKeys,\n HostStore,\n hostDynamicDsKeys,\n HostDynamicDS,\n} from '@subql/node-core';\nimport { SubqlProjectDs } from '../../configure/SubqueryProject';\nimport { IndexerManager } from '../indexer.manager';\nimport { WorkerModule } from './worker.module';\nimport {\n FetchBlockResponse,\n ProcessBlockResponse,\n WorkerService,\n WorkerStatusResponse,\n} from './worker.service';\nimport {\n HostUnfinalizedBlocks,\n hostUnfinalizedBlocksKeys,\n} from './worker.unfinalizedBlocks.service';\nlet app: INestApplication;\nlet workerService: WorkerService;\n\nconst logger = getLogger(`worker #${threadId}`);\n\nasync function initWorker(): Promise<void> {\n try {\n if (app) {\n logger.warn('Worker already initialised');\n return;\n }\n\n app = await NestFactory.create(WorkerModule, {\n logger: new NestLogger(), // TIP: If the worker is crashing comment out this line for better logging\n });\n\n await app.init();\n\n const indexerManager = app.get(IndexerManager);\n // Initialise async services, we do this here rather than in factories so we can capture one off events\n await indexerManager.start();\n\n workerService = app.get(WorkerService);\n } catch (e) {\n console.log('Failed to start worker', e);\n logger.error(e, 'Failed to start worker');\n throw e;\n }\n}\n\nasync function fetchBlock(height: number): Promise<FetchBlockResponse> {\n assert(workerService, 'Not initialised');\n return workerService.fetchBlock(height);\n}\n\nasync function processBlock(height: number): Promise<ProcessBlockResponse> {\n assert(workerService, 'Not initialised');\n\n return workerService.processBlock(height);\n}\n\n// eslint-disable-next-line @typescript-eslint/require-await\nasync function numFetchedBlocks(): Promise<number> {\n return workerService.numFetchedBlocks;\n}\n\n// eslint-disable-next-line @typescript-eslint/require-await\nasync function numFetchingBlocks(): Promise<number> {\n return workerService.numFetchingBlocks;\n}\n\n// eslint-disable-next-line @typescript-eslint/require-await\nasync function getStatus(): Promise<WorkerStatusResponse> {\n return {\n threadId,\n fetchedBlocks: workerService.numFetchedBlocks,\n toFetchBlocks: workerService.numFetchingBlocks,\n isIndexing: workerService.isIndexing,\n };\n}\n\n// eslint-disable-next-line @typescript-eslint/require-await\nasync function getMemoryLeft(): Promise<number> {\n const totalHeap = getHeapStatistics().heap_size_limit;\n const heapUsed = process.memoryUsage().heapUsed;\n\n return totalHeap - heapUsed;\n}\n\nasync function waitForWorkerBatchSize(heapSizeInBytes: number): Promise<void> {\n await waitForBatchSize(heapSizeInBytes);\n}\n\n// Register these functions to be exposed to worker host\n(global as any).host = WorkerHost.create<\n HostStore & HostDynamicDS<SubqlProjectDs> & HostUnfinalizedBlocks,\n IInitIndexerWorker\n>(\n [...hostStoreKeys, ...hostDynamicDsKeys, ...hostUnfinalizedBlocksKeys],\n {\n initWorker,\n fetchBlock,\n processBlock,\n numFetchedBlocks,\n numFetchingBlocks,\n getStatus,\n getMemoryLeft,\n waitForWorkerBatchSize,\n },\n logger,\n);\n\n// Export types to be used on the parent\ntype InitWorker = typeof initWorker;\ntype FetchBlock = typeof fetchBlock;\ntype ProcessBlock = typeof processBlock;\ntype NumFetchedBlocks = typeof numFetchedBlocks;\ntype NumFetchingBlocks = typeof numFetchingBlocks;\ntype GetWorkerStatus = typeof getStatus;\ntype GetMemoryLeft = typeof getMemoryLeft;\ntype WaitForWorkerBatchSize = typeof waitForWorkerBatchSize;\n\nexport type IIndexerWorker = {\n processBlock: ProcessBlock;\n fetchBlock: FetchBlock;\n numFetchedBlocks: NumFetchedBlocks;\n numFetchingBlocks: NumFetchingBlocks;\n getStatus: GetWorkerStatus;\n getMemoryLeft: GetMemoryLeft;\n waitForWorkerBatchSize: WaitForWorkerBatchSize;\n};\n\nexport type IInitIndexerWorker = IIndexerWorker & {\n initWorker: InitWorker;\n};\n\nprocess.on('uncaughtException', (e) => {\n logger.error(e, 'Uncaught Exception');\n throw e;\n});\n"]}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NodeConfig, IProjectService } from '@subql/node-core';
|
|
2
|
+
import { SubqlProjectDs } from '../../configure/SubqueryProject';
|
|
3
|
+
import { EthereumApiService } from '../../ethereum';
|
|
2
4
|
import { IndexerManager } from '../indexer.manager';
|
|
3
|
-
export
|
|
5
|
+
export type FetchBlockResponse = {
|
|
4
6
|
parentHash: string;
|
|
5
7
|
} | undefined;
|
|
6
|
-
export
|
|
8
|
+
export type ProcessBlockResponse = {
|
|
7
9
|
dynamicDsCreated: boolean;
|
|
8
|
-
|
|
10
|
+
blockHash: string;
|
|
11
|
+
reindexBlockHeight: number;
|
|
9
12
|
};
|
|
10
|
-
export
|
|
13
|
+
export type WorkerStatusResponse = {
|
|
11
14
|
threadId: number;
|
|
12
15
|
isIndexing: boolean;
|
|
13
16
|
fetchedBlocks: number;
|
|
@@ -16,10 +19,11 @@ export declare type WorkerStatusResponse = {
|
|
|
16
19
|
export declare class WorkerService {
|
|
17
20
|
private apiService;
|
|
18
21
|
private indexerManager;
|
|
22
|
+
private projectService;
|
|
19
23
|
private fetchedBlocks;
|
|
20
24
|
private _isIndexing;
|
|
21
25
|
private queue;
|
|
22
|
-
constructor(apiService:
|
|
26
|
+
constructor(apiService: EthereumApiService, indexerManager: IndexerManager, projectService: IProjectService<SubqlProjectDs>, nodeConfig: NodeConfig);
|
|
23
27
|
fetchBlock(height: number): Promise<FetchBlockResponse>;
|
|
24
28
|
processBlock(height: number): Promise<ProcessBlockResponse>;
|
|
25
29
|
get numFetchedBlocks(): number;
|