123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303 |
- /*
- * This file is part of the storage node for the Joystream project.
- * Copyright (C) 2019 Joystream Contributors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
- 'use strict'
- const debug = require('debug')('joystream:runtime:roles')
- const BN = require('bn.js')
- const { Text } = require('@polkadot/types')
- /*
- * Finds assigned worker id corresponding to the application id from the resulting
- * ApplicationIdToWorkerIdMap map in the OpeningFilled event. Expects map to
- * contain at least one entry.
- */
- function getWorkerIdFromApplicationIdToWorkerIdMap(filledMap, applicationId) {
- if (filledMap.size === 0) {
- throw new Error('Expected opening to be filled!')
- }
- let ourApplicationIdKey
- for (const key of filledMap.keys()) {
- if (key.eq(applicationId)) {
- ourApplicationIdKey = key
- break
- }
- }
- if (!ourApplicationIdKey) {
- throw new Error('Expected application id to have been filled!')
- }
- const workerId = filledMap.get(ourApplicationIdKey)
- return workerId
- }
- /*
- * Add worker related functionality to the substrate API.
- */
- class WorkersApi {
- static async create(base) {
- const ret = new WorkersApi()
- ret.base = base
- await ret.init()
- return ret
- }
- // eslint-disable-next-line class-methods-use-this, require-await
- async init() {
- debug('Init')
- }
- /*
- * Check whether the given account and id represent an enrolled storage provider
- */
- async isRoleAccountOfStorageProvider(storageProviderId, roleAccountId) {
- const id = new BN(storageProviderId)
- const roleAccount = this.base.identities.keyring.decodeAddress(roleAccountId)
- const providerAccount = await this.storageProviderRoleAccount(id)
- return providerAccount && providerAccount.eq(roleAccount)
- }
- /*
- * Returns true if the provider id is enrolled
- */
- async isStorageProvider(storageProviderId) {
- const worker = await this.storageWorkerByProviderId(storageProviderId)
- return worker !== null
- }
- /*
- * Returns a provider's role account or null if provider doesn't exist
- */
- async storageProviderRoleAccount(storageProviderId) {
- const worker = await this.storageWorkerByProviderId(storageProviderId)
- return worker ? worker.role_account_id : null
- }
- /*
- * Returns a Worker instance or null if provider does not exist
- */
- async storageWorkerByProviderId(storageProviderId) {
- const id = new BN(storageProviderId)
- const { providers } = await this.getAllProviders()
- return providers[id.toNumber()] || null
- }
- /*
- * Returns storage provider's general purpose storage value from chain
- */
- async getWorkerStorageValue(id) {
- const value = await this.base.api.query.storageWorkingGroup.workerStorage(id)
- return new Text(this.base.api.registry, value).toString()
- }
- /*
- * Set storage provider's general purpose storage value on chain
- */
- async setWorkerStorageValue(value) {
- const id = this.base.storageProviderId
- const tx = this.base.api.tx.storageWorkingGroup.updateRoleStorage(id, value)
- const senderAccount = await this.storageProviderRoleAccount(id)
- return this.base.signAndSend(senderAccount, tx)
- }
- /*
- * Returns the the first found provider id with a role account or null if not found
- */
- async findProviderIdByRoleAccount(roleAccount) {
- const { ids, providers } = await this.getAllProviders()
- for (let i = 0; i < ids.length; i++) {
- const id = ids[i]
- if (providers[id].role_account_id.eq(roleAccount)) {
- return id
- }
- }
- return null
- }
- /*
- * Returns the set of ids and Worker instances of providers enrolled on the network
- */
- async getAllProviders() {
- const ids = []
- const providers = {}
- const entries = await this.base.api.query.storageWorkingGroup.workerById.entries()
- entries.forEach(([storageKey, worker]) => {
- const id = storageKey.args[0].toNumber()
- ids.push(id)
- providers[id] = worker
- })
- return { ids, providers }
- }
- async getLeadRoleAccount() {
- const currentLead = await this.base.api.query.storageWorkingGroup.currentLead()
- if (currentLead.isSome) {
- const leadWorkerId = currentLead.unwrap()
- const worker = await this.base.api.query.storageWorkingGroup.workerById(leadWorkerId)
- return worker.role_account_id
- }
- return null
- }
- // Helper methods below don't really belong in the colossus runtime api library.
- // They are only used by the dev-init command in the cli to setup a development environment
- /*
- * Add a new storage group opening using the lead account. Returns the
- * new opening id.
- */
- async devAddStorageOpening(info) {
- const openTx = this.devMakeAddOpeningTx('Worker', info)
- return this.devSubmitAddOpeningTx(openTx, await this.getLeadRoleAccount())
- }
- /*
- * Add a new storage working group lead opening using sudo account. Returns the
- * new opening id.
- */
- async devAddStorageLeadOpening(info) {
- const openTx = this.devMakeAddOpeningTx('Leader', info)
- const sudoTx = this.base.api.tx.sudo.sudo(openTx)
- return this.devSubmitAddOpeningTx(sudoTx, await this.base.identities.getSudoAccount())
- }
- /*
- * Constructs an addOpening tx of openingType
- */
- devMakeAddOpeningTx(openingType, info) {
- return this.base.api.tx.storageWorkingGroup.addOpening(
- 'CurrentBlock',
- {
- application_rationing_policy: {
- max_active_applicants: 1,
- },
- max_review_period_length: 10,
- // default values for everything else..
- },
- info || 'dev-opening',
- openingType
- )
- }
- /*
- * Submits a tx (expecting it to dispatch storageWorkingGroup.addOpening) and returns
- * the OpeningId from the resulting event.
- */
- async devSubmitAddOpeningTx(tx, senderAccount) {
- return this.base.signAndSendThenGetEventResult(senderAccount, tx, {
- module: 'storageWorkingGroup',
- event: 'OpeningAdded',
- type: 'OpeningId',
- index: 0,
- })
- }
- /*
- * Apply on an opening, returns the application id.
- */
- async devApplyOnOpening(openingId, memberId, memberAccount, roleAccount) {
- const applyTx = this.base.api.tx.storageWorkingGroup.applyOnOpening(
- memberId,
- openingId,
- roleAccount,
- null,
- null,
- `colossus-${memberId}`
- )
- return this.base.signAndSendThenGetEventResult(memberAccount, applyTx, {
- module: 'storageWorkingGroup',
- event: 'AppliedOnOpening',
- type: 'ApplicationId',
- index: 1,
- })
- }
- /*
- * Move lead opening to review state using sudo account
- */
- async devBeginLeadOpeningReview(openingId) {
- const beginReviewTx = this.devMakeBeginOpeningReviewTx(openingId)
- const sudoTx = this.base.api.tx.sudo.sudo(beginReviewTx)
- return this.base.signAndSend(await this.base.identities.getSudoAccount(), sudoTx)
- }
- /*
- * Move a storage opening to review state using lead account
- */
- async devBeginStorageOpeningReview(openingId) {
- const beginReviewTx = this.devMakeBeginOpeningReviewTx(openingId)
- return this.base.signAndSend(await this.getLeadRoleAccount(), beginReviewTx)
- }
- /*
- * Constructs a beingApplicantReview tx for openingId, which puts an opening into the review state
- */
- devMakeBeginOpeningReviewTx(openingId) {
- return this.base.api.tx.storageWorkingGroup.beginApplicantReview(openingId)
- }
- /*
- * Fill a lead opening, return the assigned worker id, using the sudo account
- */
- async devFillLeadOpening(openingId, applicationId) {
- const fillTx = this.devMakeFillOpeningTx(openingId, applicationId)
- const sudoTx = this.base.api.tx.sudo.sudo(fillTx)
- const filled = await this.devSubmitFillOpeningTx(await this.base.identities.getSudoAccount(), sudoTx)
- return getWorkerIdFromApplicationIdToWorkerIdMap(filled, applicationId)
- }
- /*
- * Fill a storage opening, return the assigned worker id, using the lead account
- */
- async devFillStorageOpening(openingId, applicationId) {
- const fillTx = this.devMakeFillOpeningTx(openingId, applicationId)
- const filled = await this.devSubmitFillOpeningTx(await this.getLeadRoleAccount(), fillTx)
- return getWorkerIdFromApplicationIdToWorkerIdMap(filled, applicationId)
- }
- /*
- * Constructs a FillOpening transaction
- */
- devMakeFillOpeningTx(openingId, applicationId) {
- return this.base.api.tx.storageWorkingGroup.fillOpening(openingId, [applicationId], null)
- }
- /*
- * Dispatches a fill opening tx and returns a map of the application id to their new assigned worker ids.
- */
- async devSubmitFillOpeningTx(senderAccount, tx) {
- return this.base.signAndSendThenGetEventResult(senderAccount, tx, {
- module: 'storageWorkingGroup',
- event: 'OpeningFilled',
- type: 'ApplicationIdToWorkerIdMap',
- index: 1,
- })
- }
- }
- module.exports = {
- WorkersApi,
- }
|