Skip to content

Commit

Permalink
Merge pull request #5061 from thesan/fix/qn-validator-verification
Browse files Browse the repository at this point in the history
[QN] Fix validator verification
  • Loading branch information
mnaamani authored Feb 16, 2024
2 parents 070a8f2 + bba5dac commit fcc23c4
Show file tree
Hide file tree
Showing 10 changed files with 97 additions and 85 deletions.
2 changes: 1 addition & 1 deletion cli/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@joystream/cli",
"description": "Command Line Interface for Joystream community and governance activities",
"version": "1.2.3",
"version": "1.3.0",
"author": "Leszek Wiesner",
"bin": {
"joystream-cli": "./bin/run"
Expand Down
2 changes: 1 addition & 1 deletion cli/src/commands/working-groups/verifyValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default class VerifyValidatorCommand extends WorkingGroupsCommandBase {

async run(): Promise<void> {
const worker = await this.getRequiredWorkerContext()
if (!worker || this.group === WorkingGroups.Membership) {
if (!worker || this.group !== WorkingGroups.Membership) {
return this.error('Only membership workers can perform this command')
}

Expand Down
8 changes: 6 additions & 2 deletions query-node/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
### 1.8.2

- Fix validator profile verification - [#5061](https://github.com/Joystream/joystream/pull/5061)

### 1.8.0

- Add support for validator account verification
- Add support for validator profile verification - [#4976](https://github.com/Joystream/joystream/pull/4976)

### 1.7.0

- Refactor of mappings for more better handling of error cases. [#4856](https://github.com/Joystream/joystream/pull/4856)
- Bug fix [#4855](https://github.com/Joystream/joystream/issues/4855)
- Add support for UpdateGlobalNftLimit proposal.
- Add support for UpdateGlobalNftLimit proposal.

### 1.6.0

Expand Down
4 changes: 4 additions & 0 deletions query-node/manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,10 @@ mappings:
handler: workingGroups_WorkerStartedLeaving
- event: membershipWorkingGroup.WorkingGroupBudgetFunded
handler: workingGroups_WorkingGroupBudgetFunded
- event: membershipWorkingGroup.LeadRemarked
handler: workingGroups_LeadRemarked
- event: membershipWorkingGroup.WorkerRemarked
handler: workingGroups_WorkerRemarked
# Content directory working group
- event: contentWorkingGroup.OpeningAdded
handler: workingGroups_OpeningAdded
Expand Down
2 changes: 1 addition & 1 deletion query-node/mappings/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "query-node-mappings",
"version": "1.8.1",
"version": "1.8.2",
"description": "Mappings for hydra-processor",
"main": "lib/src/index.js",
"license": "MIT",
Expand Down
113 changes: 47 additions & 66 deletions query-node/mappings/src/workingGroups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,45 @@ function isWorkerActive(worker: Worker): boolean {
)
}

async function applyWorkingGroupsRemark(
{ store, event }: EventContext & StoreContext,
metadataByte: Bytes,
getActor: (group: WorkingGroupModuleName) => Promise<Worker>
): Promise<void> {
const group = await getWorkingGroupOrFail(store, event)

const metadata = deserializeMetadata(RemarkMetadataAction, metadataByte)
if (metadata?.moderatePost) {
if (group.name !== 'forumWorkingGroup') {
return invalidMetadata(`The ${group.name} is incompatible with the remarked moderatePost`)
}
const { postId, rationale } = metadata.moderatePost
const actor = await getActor(group.name)

const post = await getById(store, ForumPost, postId)
if (!post) {
return invalidMetadata(`Forum post not found by id: ${postId}`)
}
const eventType = actor.isLead ? 'leadRemark' : 'workerRemark'
await moderatePost(store, event, eventType, post, actor, rationale)
} else if (metadata?.verifyValidator) {
if (group.name !== 'membershipWorkingGroup') {
return invalidMetadata(`The ${group.name} can't verify the validator's membership`)
}
const { memberId, isVerified } = metadata.verifyValidator

const member = await getById(store, Membership, memberId, ['metadata'])
if (!member || !member.metadata) {
return invalidMetadata(`Membership not found by id: ${memberId}`)
}
member.metadata.isVerifiedValidator = isVerified
await store.save<MemberMetadata>(member.metadata)
await store.save<Membership>(member)
} else {
return invalidMetadata('Unrecognized remarked action')
}
}

// Mapping functions
export async function workingGroups_OpeningAdded({ store, event }: EventContext & StoreContext): Promise<void> {
const [openingRuntimeId, metadataBytes, openingType, stakePolicy, optRewardPerBlock] =
Expand Down Expand Up @@ -709,74 +748,16 @@ export async function workingGroups_StatusTextChanged({ store, event }: EventCon
await store.save<StatusTextChangedEvent>(statusTextChangedEvent)
}

export async function workingGroups_LeadRemarked({ store, event }: EventContext & StoreContext): Promise<void> {
const [metadataByte] = new WorkingGroup_LeadRemarkedEvent_V1001(event).params
const group = await getWorkingGroupOrFail(store, event)

const metadata = deserializeMetadata(RemarkMetadataAction, metadataByte)
if (metadata?.moderatePost) {
if (group.name !== 'forumWorkingGroup') {
return invalidMetadata(`The ${group.name} is incompatible with the remarked moderatePost`)
}
const { postId, rationale } = metadata.moderatePost
const actor = await getWorkingGroupLeadOrFail(store, group.name)

const post = await getById(store, ForumPost, postId)
if (!post) {
return invalidMetadata(`Forum post not found by id: ${postId}`)
}
await moderatePost(store, event, 'leadRemark', post, actor, rationale)
} else if (metadata?.verifyValidator) {
if (group.name !== 'membershipWorkingGroup') {
return invalidMetadata(`The ${group.name} can't verify the validator's membership`)
}
const { memberId, isVerified } = metadata.verifyValidator

const member = await getById(store, Membership, memberId)
if (!member) {
return invalidMetadata(`Membership not found by id: ${memberId}`)
}
member.metadata.isVerifiedValidator = isVerified
await store.save<MemberMetadata>(member.metadata)
await store.save<Membership>(member)
} else {
return invalidMetadata('Unrecognized remarked action')
}
export async function workingGroups_LeadRemarked(context: EventContext & StoreContext): Promise<void> {
const [metadataByte] = new WorkingGroup_LeadRemarkedEvent_V1001(context.event).params
const getWorker = (group: WorkingGroupModuleName) => getWorkingGroupLeadOrFail(context.store, group)
await applyWorkingGroupsRemark(context, metadataByte, getWorker)
}

export async function workingGroups_WorkerRemarked({ store, event }: EventContext & StoreContext): Promise<void> {
const [workerId, metadataByte] = new WorkingGroup_WorkerRemarkedEvent_V1001(event).params
const group = await getWorkingGroupOrFail(store, event)

const metadata = deserializeMetadata(RemarkMetadataAction, metadataByte)
if (metadata?.moderatePost) {
if (group.name !== 'forumWorkingGroup') {
return invalidMetadata(`The ${group.name} is incompatible with the remarked moderatePost`)
}
const { postId, rationale } = metadata.moderatePost
const actor = await getWorkerOrFail(store, group.name, workerId)

const post = await getById(store, ForumPost, postId)
if (!post) {
return invalidMetadata(`Forum post not found by id: ${postId}`)
}
await moderatePost(store, event, 'workerRemark', post, actor, rationale)
} else if (metadata?.verifyValidator) {
if (group.name !== 'membershipWorkingGroup') {
return invalidMetadata(`The ${group.name} can't verify the validator's membership`)
}
const { memberId, isVerified } = metadata.verifyValidator

const member = await getById(store, Membership, memberId)
if (!member) {
return invalidMetadata(`Membership not found by id: ${memberId}`)
}
member.metadata.isVerifiedValidator = isVerified
await store.save<MemberMetadata>(member.metadata)
await store.save<Membership>(member)
} else {
return invalidMetadata('Unrecognized remarked action')
}
export async function workingGroups_WorkerRemarked(context: EventContext & StoreContext): Promise<void> {
const [workerId, metadataByte] = new WorkingGroup_WorkerRemarkedEvent_V1001(context.event).params
const getWorker = (group: WorkingGroupModuleName) => getWorkerOrFail(context.store, group, workerId)
await applyWorkingGroupsRemark(context, metadataByte, getWorker)
}

export async function workingGroups_WorkerRoleAccountUpdated({
Expand Down
2 changes: 1 addition & 1 deletion query-node/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "query-node-root",
"version": "1.8.1",
"version": "1.8.2",
"description": "GraphQL server and mappings. Generated with ♥ by Hydra-CLI",
"scripts": {
"build": "./build.sh",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { AnyQueryNodeEvent, EventDetails } from '../../types'
import { Utils } from '../../utils'
import { MembershipFieldsFragment } from '../../graphql/generated/queries'
import { WithMembershipWorkersFixture } from './WithMembershipWorkersFixture'
import { assert } from 'chai'

export type VerifyValidatorInput = {
memberId: MemberId
Expand All @@ -34,8 +35,8 @@ export class VerifyValidatorMembershipFixture extends WithMembershipWorkersFixtu
const metadata = Utils.metadataToBytes(RemarkMetadataAction, {
verifyValidator: { memberId: Long.fromString(u.memberId.toString()), isVerified: u.isVerified },
})
return u.memberId
? this.api.tx.membershipWorkingGroup.workerRemark(u.memberId, metadata)
return u.asWorker
? this.api.tx.membershipWorkingGroup.workerRemark(u.asWorker, metadata)
: this.api.tx.membershipWorkingGroup.leadRemark(metadata)
})
}
Expand All @@ -49,22 +50,35 @@ export class VerifyValidatorMembershipFixture extends WithMembershipWorkersFixtu
}

private assertQueriedMembershipsAreValid(qMembers: MembershipFieldsFragment[]): void {
this.events.map((e, i) => {
this.events.forEach((e, i) => {
const verification = this.verifications[i]
if (verification.expectFailure) return

const qMembership = qMembers.find((p) => p.id === verification.memberId.toString())
Utils.assert(qMembership, 'Query node: Membership not found')

const { id, metadata } = qMembership
const isVerified = verification.isVerified
const expectedOutcome = isVerified ? 'verified' : 'unverified'
assert.equal(metadata.isVerifiedValidator, isVerified, `Member ${id} was not ${expectedOutcome}.`)
})
}

protected assertQueryNodeEventIsValid(qEvent: AnyQueryNodeEvent, i: number): void {
// TODO: implement QN checks
// NOTE: These transactions do not create any QN events
}

async runQueryNodeChecks(): Promise<void> {
await super.runQueryNodeChecks()
const qMembers = await this.query.getMembersByIds(this.verifications.map((m) => m.memberId))
this.assertQueriedMembershipsAreValid(qMembers)

const memberIds = [...new Set(this.verifications.map((v) => v.memberId))]
await this.query.tryQueryWithTimeout(
() => this.query.getMembersByIds(memberIds),
(qMembers) => {
const missingMemberIds = memberIds.map(String).filter((id) => !qMembers.some((member) => member.id === id))
assert.equal(missingMemberIds.length, 0, `Query node: missing memberships: [${missingMemberIds.join(', ')}]`)
this.assertQueriedMembershipsAreValid(qMembers)
}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,27 @@ export default async function updateValidatorVerificationStatus({ api, query }:
debug('Started')
api.enableDebugTxLogs()

const workerId = await api.query.membershipWorkingGroup.currentLead()
if (workerId.isNone) {
throw new Error('Membership working group lead not set!')
}

const accounts = (await api.createKeyPairs(2)).map(({ key }) => key.address)
const buyMembershipsFixture = new BuyMembershipHappyCaseFixture(api, query, accounts)
await new FixtureRunner(buyMembershipsFixture).run()
const memberIds = buyMembershipsFixture.getCreatedMembers()

const updates1: VerifyValidatorInput[] = buyMembershipsFixture
.getCreatedMembers()
.map((memberId) => ({ memberId, isVerified: true }))

const updates1: VerifyValidatorInput[] = [
{ memberId: memberIds[0], isVerified: true },
{ memberId: memberIds[1], isVerified: true, asWorker: workerId.unwrap() },
]
const verifyFixture = new VerifyValidatorMembershipFixture(api, query, updates1)
await new FixtureRunner(verifyFixture).runWithQueryNodeChecks()

const updates2 = updates1.map(({ memberId }) => ({ memberId, isVerified: false }))

const updates2: VerifyValidatorInput[] = [
{ memberId: memberIds[0], isVerified: false },
{ memberId: memberIds[1], isVerified: false, asWorker: workerId.unwrap() },
]
const unverifyFixture = new VerifyValidatorMembershipFixture(api, query, updates2)
await new FixtureRunner(unverifyFixture).runWithQueryNodeChecks()

Expand Down
2 changes: 1 addition & 1 deletion tests/network-tests/src/scenarios/memberships.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import electCouncil from '../flows/council/elect'
// eslint-disable-next-line @typescript-eslint/no-floating-promises
scenario('Memberships', async ({ job }) => {
const councilJob = job('electing council', electCouncil)
const hireLeads = job('hire leads', leadOpening()).after(councilJob)
const hireLeads = job('hire leads', leadOpening(true, ['membershipWorkingGroup'])).after(councilJob)

// All other job should be executed after, otherwise changing membershipPrice etc. may break them
job('buying members', buyingMemberships)
Expand Down

0 comments on commit fcc23c4

Please sign in to comment.