Skip to content

Commit b991e8f

Browse files
committed
index gov proposal votes
1 parent ff0ad71 commit b991e8f

File tree

23 files changed

+1386
-103
lines changed

23 files changed

+1386
-103
lines changed

src/db/connection.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import {
1717
ComputationDependency,
1818
Contract,
1919
DistributionCommunityPoolStateEvent,
20-
GovStateEvent,
20+
GovProposal,
21+
GovProposalVote,
2122
StakingSlashEvent,
2223
State,
2324
Validator,
@@ -45,7 +46,8 @@ const getModelsForType = (type: DbType): SequelizeOptions['models'] =>
4546
ComputationDependency,
4647
Contract,
4748
DistributionCommunityPoolStateEvent,
48-
GovStateEvent,
49+
GovProposal,
50+
GovProposalVote,
4951
StakingSlashEvent,
5052
State,
5153
Validator,

src/db/dependable.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { ComputationDependentKey, DependableEventModel } from '@/types'
33
import {
44
BankStateEvent,
55
DistributionCommunityPoolStateEvent,
6-
GovStateEvent,
6+
GovProposal,
7+
GovProposalVote,
78
StakingSlashEvent,
89
WasmStateEvent,
910
WasmStateEventTransformation,
@@ -16,7 +17,8 @@ export const getDependableEventModels = (): typeof DependableEventModel[] => [
1617
WasmTxEvent,
1718
StakingSlashEvent,
1819
BankStateEvent,
19-
GovStateEvent,
20+
GovProposal,
21+
GovProposalVote,
2022
DistributionCommunityPoolStateEvent,
2123
]
2224

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { QueryInterface } from 'sequelize'
2+
3+
module.exports = {
4+
async up(queryInterface: QueryInterface) {
5+
await queryInterface.renameTable('GovStateEvents', 'GovProposals')
6+
},
7+
8+
async down(queryInterface: QueryInterface) {
9+
await queryInterface.renameTable('GovProposals', 'GovStateEvents')
10+
},
11+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { QueryInterface, fn } from 'sequelize'
2+
import { DataType } from 'sequelize-typescript'
3+
4+
module.exports = {
5+
async up(queryInterface: QueryInterface) {
6+
await queryInterface.createTable('GovProposalVotes', {
7+
id: {
8+
primaryKey: true,
9+
autoIncrement: true,
10+
type: DataType.INTEGER,
11+
},
12+
proposalId: {
13+
allowNull: false,
14+
type: DataType.BIGINT,
15+
},
16+
voterAddress: {
17+
allowNull: false,
18+
type: DataType.STRING,
19+
},
20+
blockHeight: {
21+
allowNull: false,
22+
type: DataType.BIGINT,
23+
},
24+
blockTimeUnixMs: {
25+
allowNull: false,
26+
type: DataType.BIGINT,
27+
},
28+
blockTimestamp: {
29+
allowNull: false,
30+
type: DataType.DATE,
31+
},
32+
data: {
33+
allowNull: false,
34+
type: DataType.TEXT,
35+
},
36+
createdAt: {
37+
allowNull: false,
38+
type: DataType.DATE,
39+
defaultValue: fn('NOW'),
40+
},
41+
updatedAt: {
42+
allowNull: false,
43+
type: DataType.DATE,
44+
defaultValue: fn('NOW'),
45+
},
46+
})
47+
await queryInterface.addIndex('GovProposalVotes', {
48+
unique: true,
49+
fields: ['blockHeight', 'proposalId', 'voterAddress'],
50+
})
51+
await queryInterface.addIndex('GovProposalVotes', {
52+
fields: ['proposalId'],
53+
})
54+
await queryInterface.addIndex('GovProposalVotes', {
55+
fields: ['voterAddress'],
56+
})
57+
await queryInterface.addIndex('GovProposalVotes', {
58+
fields: ['blockHeight'],
59+
})
60+
await queryInterface.addIndex('GovProposalVotes', {
61+
fields: ['blockTimeUnixMs'],
62+
})
63+
},
64+
async down(queryInterface: QueryInterface) {
65+
await queryInterface.dropTable('GovProposalVotes')
66+
},
67+
}

src/db/models/Computation.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,9 @@ export class Computation extends Model {
147147
await Promise.all(
148148
getDependableEventModels().map(async (DependableEventModel) => {
149149
const namespacedKeys = this.dependencies.filter(({ key }) =>
150-
key.startsWith(DependableEventModel.dependentKeyNamespace)
150+
key.startsWith(
151+
DependableEventModel.dependentKeyNamespace + ':'
152+
)
151153
)
152154
if (namespacedKeys.length === 0) {
153155
return null

src/db/models/GovStateEvent.ts renamed to src/db/models/GovProposal.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import { getDependentKey } from '@/utils'
3434
},
3535
],
3636
})
37-
export class GovStateEvent extends DependableEventModel {
37+
export class GovProposal extends DependableEventModel {
3838
@AllowNull(false)
3939
@Column(DataType.BIGINT)
4040
declare proposalId: string
@@ -64,16 +64,16 @@ export class GovStateEvent extends DependableEventModel {
6464
}
6565

6666
get dependentKey(): string {
67-
return getDependentKey(GovStateEvent.dependentKeyNamespace, this.proposalId)
67+
return getDependentKey(GovProposal.dependentKeyNamespace, this.proposalId)
6868
}
6969

7070
// Get the previous event for this proposalId. If this is the first event for
7171
// this proposalId, return null. Cache the result so it can be reused since
7272
// this shouldn't change.
73-
previousEvent?: GovStateEvent | null
74-
async getPreviousEvent(cache = true): Promise<GovStateEvent | null> {
73+
previousEvent?: GovProposal | null
74+
async getPreviousEvent(cache = true): Promise<GovProposal | null> {
7575
if (this.previousEvent === undefined || !cache) {
76-
this.previousEvent = await GovStateEvent.findOne({
76+
this.previousEvent = await GovProposal.findOne({
7777
where: {
7878
proposalId: this.proposalId,
7979
blockHeight: {
@@ -87,7 +87,7 @@ export class GovStateEvent extends DependableEventModel {
8787
return this.previousEvent
8888
}
8989

90-
static dependentKeyNamespace = DependentKeyNamespace.GovStateEvent
90+
static dependentKeyNamespace = DependentKeyNamespace.GovProposal
9191
static blockHeightKey: string = 'blockHeight'
9292
static blockTimeUnixMsKey: string = 'blockTimeUnixMs'
9393

src/db/models/GovProposalVote.ts

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import { Op, WhereOptions } from 'sequelize'
2+
import { AllowNull, Column, DataType, Table } from 'sequelize-typescript'
3+
4+
import {
5+
Block,
6+
ComputationDependentKey,
7+
DependableEventModel,
8+
DependentKeyNamespace,
9+
} from '@/types'
10+
import { getDependentKey } from '@/utils'
11+
12+
@Table({
13+
timestamps: true,
14+
indexes: [
15+
// Only one vote can be cast for a proposal ID by a voter at a given block
16+
// height. This ensures events are not duplicated if they attempt exporting
17+
// multiple times.
18+
{
19+
unique: true,
20+
fields: ['blockHeight', 'proposalId', 'voterAddress'],
21+
},
22+
{
23+
fields: ['proposalId'],
24+
},
25+
{
26+
fields: ['voterAddress'],
27+
},
28+
{
29+
// Speed up ordering queries.
30+
fields: ['blockHeight'],
31+
},
32+
{
33+
// Speed up ordering queries.
34+
fields: ['blockTimeUnixMs'],
35+
},
36+
],
37+
})
38+
export class GovProposalVote extends DependableEventModel {
39+
@AllowNull(false)
40+
@Column(DataType.BIGINT)
41+
declare proposalId: string
42+
43+
@AllowNull(false)
44+
@Column(DataType.STRING)
45+
declare voterAddress: string
46+
47+
@AllowNull(false)
48+
@Column(DataType.BIGINT)
49+
declare blockHeight: string
50+
51+
@AllowNull(false)
52+
@Column(DataType.BIGINT)
53+
declare blockTimeUnixMs: string
54+
55+
@AllowNull(false)
56+
@Column(DataType.DATE)
57+
declare blockTimestamp: Date
58+
59+
// Base64-encoded protobuf data.
60+
@AllowNull(false)
61+
@Column(DataType.TEXT)
62+
declare data: string
63+
64+
get block(): Block {
65+
return {
66+
height: BigInt(this.blockHeight),
67+
timeUnixMs: BigInt(this.blockTimeUnixMs),
68+
}
69+
}
70+
71+
get dependentKey(): string {
72+
return getDependentKey(
73+
GovProposalVote.dependentKeyNamespace,
74+
this.proposalId,
75+
this.voterAddress
76+
)
77+
}
78+
79+
// Get the previous event for this proposalId. If this is the first event for
80+
// this proposalId, return null. Cache the result so it can be reused since
81+
// this shouldn't change.
82+
previousEvent?: GovProposalVote | null
83+
async getPreviousEvent(cache = true): Promise<GovProposalVote | null> {
84+
if (this.previousEvent === undefined || !cache) {
85+
this.previousEvent = await GovProposalVote.findOne({
86+
where: {
87+
proposalId: this.proposalId,
88+
voterAddress: this.voterAddress,
89+
blockHeight: {
90+
[Op.lt]: this.blockHeight,
91+
},
92+
},
93+
order: [['blockHeight', 'DESC']],
94+
})
95+
}
96+
97+
return this.previousEvent
98+
}
99+
100+
static dependentKeyNamespace = DependentKeyNamespace.GovProposalVote
101+
static blockHeightKey: string = 'blockHeight'
102+
static blockTimeUnixMsKey: string = 'blockTimeUnixMs'
103+
104+
// Returns a where clause that will match all events that are described by the
105+
// dependent keys.
106+
static getWhereClauseForDependentKeys(
107+
dependentKeys: ComputationDependentKey[]
108+
): WhereOptions {
109+
const dependentKeysByProposalId = dependentKeys.reduce(
110+
(acc, dependentKey) => {
111+
// 1. Remove namespace from key.
112+
const key = dependentKey.key.replace(
113+
new RegExp(`^${this.dependentKeyNamespace}:`),
114+
''
115+
)
116+
117+
// 2. Extract proposalId from key.
118+
// Dependent keys for any proposal start with "*:".
119+
const proposalId = key.startsWith('*:') ? '' : key.split(':')[0]
120+
121+
const voterAddress = key
122+
// 3. Remove proposalId from key.
123+
.replace(new RegExp(`^${proposalId || '\\*'}:`), '')
124+
// 4. Replace wildcard symbol with LIKE wildcard for database query.
125+
.replace(/\*/g, '%')
126+
127+
return {
128+
...acc,
129+
[proposalId]: [
130+
...(acc[proposalId] ?? []),
131+
{
132+
voterAddress,
133+
prefix: dependentKey.prefix,
134+
},
135+
],
136+
}
137+
},
138+
{} as Record<string, { voterAddress: string; prefix: boolean }[]>
139+
)
140+
141+
return {
142+
[Op.or]: Object.entries(dependentKeysByProposalId).map(
143+
([proposalId, keys]) => {
144+
const exactKeys = keys
145+
.filter(
146+
({ voterAddress, prefix }) =>
147+
!prefix && !voterAddress.includes('%')
148+
)
149+
.map(({ voterAddress }) => voterAddress)
150+
const wildcardKeys = keys
151+
.filter(
152+
({ voterAddress, prefix }) => prefix || voterAddress.includes('%')
153+
)
154+
.map(
155+
({ voterAddress, prefix }) => voterAddress + (prefix ? '%' : '')
156+
)
157+
158+
return {
159+
// Only include if proposalId is defined.
160+
...(proposalId && { proposalId }),
161+
// Related logic in `makeComputationDependencyWhere` in
162+
// `src/db/computation.ts`.
163+
voterAddress: {
164+
[Op.or]: [
165+
// Exact matches.
166+
...(exactKeys.length > 0 ? [{ [Op.in]: exactKeys }] : []),
167+
// Wildcards. May or may not be prefixes.
168+
...wildcardKeys.map((voterAddress) => ({
169+
[Op.like]: voterAddress,
170+
})),
171+
],
172+
},
173+
}
174+
}
175+
),
176+
}
177+
}
178+
}

src/db/models/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ export * from './Computation'
1111
export * from './ComputationDependency'
1212
export * from './Contract'
1313
export * from './DistributionCommunityPoolStateEvent'
14-
export * from './GovStateEvent'
14+
export * from './GovProposal'
15+
export * from './GovProposalVote'
1516
export * from './StakingSlashEvent'
1617
export * from './State'
1718
export * from './Validator'

src/formulas/compute.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ export const computeRange = async ({
246246
await Promise.all(
247247
getDependableEventModels().map(async (DependableEventModel) => {
248248
const namespacedDependentKeys = newDependentKeys.filter(({ key }) =>
249-
key.startsWith(DependableEventModel.dependentKeyNamespace)
249+
key.startsWith(DependableEventModel.dependentKeyNamespace + ':')
250250
)
251251
if (namespacedDependentKeys.length === 0) {
252252
return []

0 commit comments

Comments
 (0)