-
Notifications
You must be signed in to change notification settings - Fork 358
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
multi chain rewind service #2673
base: main
Are you sure you want to change the base?
Changes from 3 commits
aa851f4
4190451
56ea202
c4063a7
2694b55
c5e798a
8be26fa
3a1bd7c
be995ac
c05acd8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -184,4 +184,8 @@ describe('sync helper test', () => { | |
); | ||
}, 10_000); | ||
}); | ||
|
||
describe('rewind lock', () => { | ||
// TODO | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// Copyright 2020-2024 SubQuery Pte Ltd authors & contributors | ||
// SPDX-License-Identifier: GPL-3.0 | ||
|
||
import {blake2AsHex} from '@subql/utils'; | ||
import {BuildOptions, DataTypes, Model, Sequelize} from '@subql/x-sequelize'; | ||
|
||
export const RewindTimestampKeyPrefix = 'rewindTimestamp'; | ||
export const RewindLockKey = 'rewindLock'; | ||
export type RewindTimestampKey = `${typeof RewindTimestampKeyPrefix}_${string}`; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the string portion of this a chainId? Can you comment what sort of values are expected please There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes |
||
|
||
export type RewindLockInfo = { | ||
/** Timestamp to rewind to. */ | ||
timestamp: number; | ||
chainNum: number; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, it means the number of other chains that need to wait for rollback. Maybe we need to rename it. |
||
}; | ||
export interface GlobalDataKeys { | ||
rewindLock: RewindLockInfo; | ||
[key: RewindTimestampKey]: number; | ||
} | ||
|
||
export interface GlobalData<k extends keyof GlobalDataKeys = keyof GlobalDataKeys> { | ||
key: k; | ||
value: GlobalDataKeys[k]; | ||
} | ||
|
||
interface GlobalDataEntity extends Model<GlobalData>, GlobalData {} | ||
|
||
export type GlobalDataRepo = typeof Model & { | ||
new (values?: unknown, options?: BuildOptions): GlobalDataEntity; | ||
}; | ||
|
||
export function GlobalDataFactory(sequelize: Sequelize, schema: string): GlobalDataRepo { | ||
const tableName = '_global'; | ||
|
||
return <GlobalDataRepo>sequelize.define( | ||
tableName, | ||
{ | ||
key: { | ||
type: DataTypes.STRING, | ||
primaryKey: true, | ||
}, | ||
value: { | ||
type: DataTypes.JSONB, | ||
}, | ||
Comment on lines
+42
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Whats the purpose of this being JSONB? Looking at the rest of the code it would simplify the queries to have well defined columns |
||
}, | ||
{freezeTableName: true, schema: schema} | ||
); | ||
} | ||
|
||
export function generateRewindTimestampKey(chainId: string): RewindTimestampKey { | ||
return `${RewindTimestampKeyPrefix}_${blake2AsHex(chainId)})`.substring(0, 63) as RewindTimestampKey; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this hash and substring necessary? We use it in other places such as table or function names because of postgres limitations but in this case because its data I don't see why these steps are necessary. |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,3 +3,4 @@ | |
|
||
export * from './Poi.entity'; | ||
export * from './Metadata.entity'; | ||
export * from './GlobalData.entity'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,19 +3,20 @@ | |
|
||
import assert from 'assert'; | ||
import {Inject, Injectable, OnApplicationShutdown} from '@nestjs/common'; | ||
import {EventEmitter2} from '@nestjs/event-emitter'; | ||
import {EventEmitter2, OnEvent} from '@nestjs/event-emitter'; | ||
import {SchedulerRegistry} from '@nestjs/schedule'; | ||
import {BaseDataSource} from '@subql/types-core'; | ||
import {range} from 'lodash'; | ||
import {IBlockchainService} from '../blockchain.service'; | ||
import {NodeConfig} from '../configure'; | ||
import {IndexerEvent} from '../events'; | ||
import {EventPayload, IndexerEvent, MultiChainRewindEvent, MultiChainRewindPayload} from '../events'; | ||
import {getLogger} from '../logger'; | ||
import {delay, filterBypassBlocks, getModulos} from '../utils'; | ||
import {IBlockDispatcher} from './blockDispatcher'; | ||
import {mergeNumAndBlocksToNums} from './dictionary'; | ||
import {DictionaryService} from './dictionary/dictionary.service'; | ||
import {mergeNumAndBlocks} from './dictionary/utils'; | ||
import {IMultiChainHandler, MultiChainRewindService, RewindStatus} from './multiChainRewind.service'; | ||
import {IStoreModelProvider} from './storeModelProvider'; | ||
import {BypassBlocks, IBlock, IProjectService} from './types'; | ||
import {IUnfinalizedBlocksServiceUtil} from './unfinalizedBlocks.service'; | ||
|
@@ -24,7 +25,7 @@ const logger = getLogger('FetchService'); | |
|
||
@Injectable() | ||
export class FetchService<DS extends BaseDataSource, B extends IBlockDispatcher<FB>, FB> | ||
implements OnApplicationShutdown | ||
implements OnApplicationShutdown, IMultiChainHandler | ||
{ | ||
private _latestBestHeight?: number; | ||
private _latestFinalizedHeight?: number; | ||
|
@@ -39,7 +40,8 @@ export class FetchService<DS extends BaseDataSource, B extends IBlockDispatcher< | |
private schedulerRegistry: SchedulerRegistry, | ||
@Inject('IUnfinalizedBlocksService') private unfinalizedBlocksService: IUnfinalizedBlocksServiceUtil, | ||
@Inject('IStoreModelProvider') private storeModelProvider: IStoreModelProvider, | ||
@Inject('IBlockchainService') private blockchainSevice: IBlockchainService<DS> | ||
@Inject('IBlockchainService') private blockchainSevice: IBlockchainService<DS>, | ||
private multiChainRewindService: MultiChainRewindService | ||
) {} | ||
|
||
private get latestBestHeight(): number { | ||
|
@@ -196,6 +198,14 @@ export class FetchService<DS extends BaseDataSource, B extends IBlockDispatcher< | |
// Update the target height, this happens here to stay in sync with the rest of indexing | ||
void this.storeModelProvider.metadata.set('targetHeight', latestHeight); | ||
|
||
// If we're rewinding, we should wait until it's done | ||
const multiChainStatus = this.multiChainRewindService.getStatus(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There needs to be more places to pause indexing to allow a multichain rewind. The fetch service just enqueues blocks, but the block dispatcher and indexer manager need to also stop fetching blocks and indexing current blocks. |
||
if (RewindStatus.Normal !== multiChainStatus) { | ||
logger.info(`Wait for all chains to complete rewind, current chainId: ${this.multiChainRewindService.chainId}`); | ||
yoozo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
await delay(3); | ||
yoozo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
continue; | ||
} | ||
|
||
// This could be latestBestHeight, dictionary should never include finalized blocks | ||
// TODO add buffer so dictionary not used when project synced | ||
if (startBlockHeight < this.latestBestHeight - scaledBatchSize) { | ||
|
@@ -383,4 +393,11 @@ export class FetchService<DS extends BaseDataSource, B extends IBlockDispatcher< | |
this.updateDictionary(); | ||
this.blockDispatcher.flushQueue(blockHeight); | ||
} | ||
|
||
@OnEvent(MultiChainRewindEvent.Rewind) | ||
@OnEvent(MultiChainRewindEvent.RewindTimestampDecreased) | ||
handleMultiChainRewindEvent(payload: MultiChainRewindPayload) { | ||
logger.info(`Received rewind event, height: ${payload.height}`); | ||
this.resetForNewDs(payload.height); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe this function needs to be renamed if it has other uses now. |
||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this?
getHeaderForHeight
should always return a timestamp but there is one substrate chain that doesn't have timestamps in blocks so that is why it is optional