-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
23 changed files
with
1,391 additions
and
103 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
11 changes: 11 additions & 0 deletions
11
src/db/migrations/20240816161812-rename-gov-state-event-to-gov-proposal.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { QueryInterface } from 'sequelize' | ||
|
||
module.exports = { | ||
async up(queryInterface: QueryInterface) { | ||
await queryInterface.renameTable('GovStateEvents', 'GovProposals') | ||
}, | ||
|
||
async down(queryInterface: QueryInterface) { | ||
await queryInterface.renameTable('GovProposals', 'GovStateEvents') | ||
}, | ||
} |
67 changes: 67 additions & 0 deletions
67
src/db/migrations/20240816165030-create-gov-proposal-vote.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import { QueryInterface, fn } from 'sequelize' | ||
import { DataType } from 'sequelize-typescript' | ||
|
||
module.exports = { | ||
async up(queryInterface: QueryInterface) { | ||
await queryInterface.createTable('GovProposalVotes', { | ||
id: { | ||
primaryKey: true, | ||
autoIncrement: true, | ||
type: DataType.INTEGER, | ||
}, | ||
proposalId: { | ||
allowNull: false, | ||
type: DataType.BIGINT, | ||
}, | ||
voterAddress: { | ||
allowNull: false, | ||
type: DataType.STRING, | ||
}, | ||
blockHeight: { | ||
allowNull: false, | ||
type: DataType.BIGINT, | ||
}, | ||
blockTimeUnixMs: { | ||
allowNull: false, | ||
type: DataType.BIGINT, | ||
}, | ||
blockTimestamp: { | ||
allowNull: false, | ||
type: DataType.DATE, | ||
}, | ||
data: { | ||
allowNull: false, | ||
type: DataType.TEXT, | ||
}, | ||
createdAt: { | ||
allowNull: false, | ||
type: DataType.DATE, | ||
defaultValue: fn('NOW'), | ||
}, | ||
updatedAt: { | ||
allowNull: false, | ||
type: DataType.DATE, | ||
defaultValue: fn('NOW'), | ||
}, | ||
}) | ||
await queryInterface.addIndex('GovProposalVotes', { | ||
unique: true, | ||
fields: ['blockHeight', 'proposalId', 'voterAddress'], | ||
}) | ||
await queryInterface.addIndex('GovProposalVotes', { | ||
fields: ['proposalId'], | ||
}) | ||
await queryInterface.addIndex('GovProposalVotes', { | ||
fields: ['voterAddress'], | ||
}) | ||
await queryInterface.addIndex('GovProposalVotes', { | ||
fields: ['blockHeight'], | ||
}) | ||
await queryInterface.addIndex('GovProposalVotes', { | ||
fields: ['blockTimeUnixMs'], | ||
}) | ||
}, | ||
async down(queryInterface: QueryInterface) { | ||
await queryInterface.dropTable('GovProposalVotes') | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
import { Op, WhereOptions } from 'sequelize' | ||
import { AllowNull, Column, DataType, Table } from 'sequelize-typescript' | ||
|
||
import { | ||
Block, | ||
ComputationDependentKey, | ||
DependableEventModel, | ||
DependentKeyNamespace, | ||
} from '@/types' | ||
import { getDependentKey } from '@/utils' | ||
|
||
@Table({ | ||
timestamps: true, | ||
indexes: [ | ||
// Only one vote can be cast for a proposal ID by a voter at a given block | ||
// height. This ensures events are not duplicated if they attempt exporting | ||
// multiple times. | ||
{ | ||
unique: true, | ||
fields: ['blockHeight', 'proposalId', 'voterAddress'], | ||
}, | ||
{ | ||
fields: ['proposalId'], | ||
}, | ||
{ | ||
fields: ['voterAddress'], | ||
}, | ||
{ | ||
// Speed up ordering queries. | ||
fields: ['blockHeight'], | ||
}, | ||
{ | ||
// Speed up ordering queries. | ||
fields: ['blockTimeUnixMs'], | ||
}, | ||
], | ||
}) | ||
export class GovProposalVote extends DependableEventModel { | ||
@AllowNull(false) | ||
@Column(DataType.BIGINT) | ||
declare proposalId: string | ||
|
||
@AllowNull(false) | ||
@Column(DataType.STRING) | ||
declare voterAddress: string | ||
|
||
@AllowNull(false) | ||
@Column(DataType.BIGINT) | ||
declare blockHeight: string | ||
|
||
@AllowNull(false) | ||
@Column(DataType.BIGINT) | ||
declare blockTimeUnixMs: string | ||
|
||
@AllowNull(false) | ||
@Column(DataType.DATE) | ||
declare blockTimestamp: Date | ||
|
||
// Base64-encoded protobuf data. | ||
@AllowNull(false) | ||
@Column(DataType.TEXT) | ||
declare data: string | ||
|
||
get block(): Block { | ||
return { | ||
height: BigInt(this.blockHeight), | ||
timeUnixMs: BigInt(this.blockTimeUnixMs), | ||
} | ||
} | ||
|
||
get dependentKey(): string { | ||
return getDependentKey( | ||
GovProposalVote.dependentKeyNamespace, | ||
this.proposalId, | ||
this.voterAddress | ||
) | ||
} | ||
|
||
// Get the previous event for this proposalId. If this is the first event for | ||
// this proposalId, return null. Cache the result so it can be reused since | ||
// this shouldn't change. | ||
previousEvent?: GovProposalVote | null | ||
async getPreviousEvent(cache = true): Promise<GovProposalVote | null> { | ||
if (this.previousEvent === undefined || !cache) { | ||
this.previousEvent = await GovProposalVote.findOne({ | ||
where: { | ||
proposalId: this.proposalId, | ||
voterAddress: this.voterAddress, | ||
blockHeight: { | ||
[Op.lt]: this.blockHeight, | ||
}, | ||
}, | ||
order: [['blockHeight', 'DESC']], | ||
}) | ||
} | ||
|
||
return this.previousEvent | ||
} | ||
|
||
static dependentKeyNamespace = DependentKeyNamespace.GovProposalVote | ||
static blockHeightKey: string = 'blockHeight' | ||
static blockTimeUnixMsKey: string = 'blockTimeUnixMs' | ||
|
||
// Returns a where clause that will match all events that are described by the | ||
// dependent keys. | ||
static getWhereClauseForDependentKeys( | ||
dependentKeys: ComputationDependentKey[] | ||
): WhereOptions { | ||
const dependentKeysByProposalId = dependentKeys.reduce( | ||
(acc, dependentKey) => { | ||
// 1. Remove namespace from key. | ||
const key = dependentKey.key.replace( | ||
new RegExp(`^${this.dependentKeyNamespace}:`), | ||
'' | ||
) | ||
|
||
// 2. Extract proposalId from key. | ||
// Dependent keys for any proposal start with "*:". | ||
const proposalId = key.startsWith('*:') ? '' : key.split(':')[0] | ||
|
||
const voterAddress = key | ||
// 3. Remove proposalId from key. | ||
.replace(new RegExp(`^${proposalId || '\\*'}:`), '') | ||
// 4. Replace wildcard symbol with LIKE wildcard for database query. | ||
.replace(/\*/g, '%') | ||
|
||
return { | ||
...acc, | ||
[proposalId]: [ | ||
...(acc[proposalId] ?? []), | ||
{ | ||
voterAddress, | ||
prefix: dependentKey.prefix, | ||
}, | ||
], | ||
} | ||
}, | ||
{} as Record<string, { voterAddress: string; prefix: boolean }[]> | ||
) | ||
|
||
return { | ||
[Op.or]: Object.entries(dependentKeysByProposalId).map( | ||
([proposalId, keys]) => { | ||
const exactKeys = keys | ||
.filter( | ||
({ voterAddress, prefix }) => | ||
!prefix && !voterAddress.includes('%') | ||
) | ||
.map(({ voterAddress }) => voterAddress) | ||
const wildcardKeys = keys | ||
.filter( | ||
({ voterAddress, prefix }) => prefix || voterAddress.includes('%') | ||
) | ||
.map( | ||
({ voterAddress, prefix }) => voterAddress + (prefix ? '%' : '') | ||
) | ||
|
||
return { | ||
// Only include if proposalId is defined. | ||
...(proposalId && { proposalId }), | ||
// Related logic in `makeComputationDependencyWhere` in | ||
// `src/db/computation.ts`. | ||
voterAddress: { | ||
[Op.or]: [ | ||
// Exact matches. | ||
...(exactKeys.length > 0 ? [{ [Op.in]: exactKeys }] : []), | ||
// Wildcards. May or may not be prefixes. | ||
...wildcardKeys.map((voterAddress) => ({ | ||
[Op.like]: voterAddress, | ||
})), | ||
], | ||
}, | ||
} | ||
} | ||
), | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.