assets.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. 'use strict'
  2. const debug = require('debug')('joystream:runtime:assets')
  3. const { decodeAddress } = require('@polkadot/keyring')
  4. const { StorageObjectOwner, DataObject } = require('@joystream/types/storage')
  5. function parseContentId(contentId) {
  6. try {
  7. return decodeAddress(contentId)
  8. } catch (err) {
  9. return contentId
  10. }
  11. }
  12. /*
  13. * Add asset related functionality to the substrate API.
  14. */
  15. class AssetsApi {
  16. static async create(base) {
  17. const ret = new AssetsApi()
  18. ret.base = base
  19. await AssetsApi.init()
  20. return ret
  21. }
  22. static async init() {
  23. debug('Init')
  24. }
  25. /*
  26. * Create and return a data object.
  27. */
  28. async createDataObject(accountId, memberId, contentId, doTypeId, size, ipfsCid) {
  29. contentId = parseContentId(contentId)
  30. const owner = {
  31. Member: memberId,
  32. }
  33. const content = [
  34. {
  35. content_id: contentId,
  36. type_id: doTypeId,
  37. size,
  38. ipfs_content_id: ipfsCid,
  39. },
  40. ]
  41. const tx = this.base.api.tx.dataDirectory.addContent(owner, content)
  42. await this.base.signAndSend(accountId, tx)
  43. // If the data object constructed properly, we should now be able to return
  44. // the data object from the state.
  45. return this.getDataObject(contentId)
  46. }
  47. /*
  48. * Returns the Data Object for a contendId.
  49. * Returns null if it doesn't exist.
  50. */
  51. async getDataObject(contentId) {
  52. contentId = parseContentId(contentId)
  53. // check if contentId key exists in map
  54. const storageSize = await this.base.api.query.dataDirectory.dataByContentId.size(contentId)
  55. if (storageSize.eq(0)) {
  56. return null
  57. }
  58. return this.base.api.query.dataDirectory.dataByContentId(contentId)
  59. }
  60. /*
  61. * Verify the liaison state for a DataObject:
  62. * - Check the content ID has a DataObject
  63. * - Check the storageProviderId is the liaison
  64. * - Check the liaison state is Pending
  65. *
  66. * Each failure errors out, success returns the data object.
  67. */
  68. async checkLiaisonForDataObject(storageProviderId, contentId) {
  69. contentId = parseContentId(contentId)
  70. const obj = await this.getDataObject(contentId)
  71. if (!obj) {
  72. throw new Error(`No DataObject found for content ID: ${contentId}`)
  73. }
  74. if (!obj.liaison.eq(storageProviderId)) {
  75. throw new Error(`This storage node is not liaison for the content ID: ${contentId}`)
  76. }
  77. if (obj.liaison_judgement.type !== 'Pending') {
  78. throw new Error(`Content upload has already been processed.`)
  79. }
  80. return obj
  81. }
  82. /*
  83. * Sets the data object liaison judgement to Accepted
  84. */
  85. async acceptContent(providerAccoundId, storageProviderId, contentId) {
  86. contentId = parseContentId(contentId)
  87. const tx = this.base.api.tx.dataDirectory.acceptContent(storageProviderId, contentId)
  88. return this.base.signAndSend(providerAccoundId, tx)
  89. }
  90. /*
  91. * Gets storage relationship for contentId for the given provider
  92. */
  93. async getStorageRelationshipAndId(storageProviderId, contentId) {
  94. contentId = parseContentId(contentId)
  95. const rids = await this.base.api.query.dataObjectStorageRegistry.relationshipsByContentId(contentId)
  96. while (rids.length) {
  97. const relationshipId = rids.shift()
  98. let relationship = await this.base.api.query.dataObjectStorageRegistry.relationships(relationshipId)
  99. relationship = relationship.unwrap()
  100. if (relationship.storage_provider.eq(storageProviderId)) {
  101. return { relationship, relationshipId }
  102. }
  103. }
  104. return {}
  105. }
  106. /*
  107. * Creates storage relationship for a data object and provider and
  108. * returns the relationship id
  109. */
  110. async createStorageRelationship(providerAccountId, storageProviderId, contentId) {
  111. contentId = parseContentId(contentId)
  112. const tx = this.base.api.tx.dataObjectStorageRegistry.addRelationship(storageProviderId, contentId)
  113. return this.base.signAndSendThenGetEventResult(providerAccountId, tx, {
  114. module: 'dataObjectStorageRegistry',
  115. event: 'DataObjectStorageRelationshipAdded',
  116. type: 'DataObjectStorageRelationshipId',
  117. index: 0,
  118. })
  119. }
  120. /*
  121. * Set the ready state for a data object storage relationship to the new value
  122. */
  123. async toggleStorageRelationshipReady(providerAccountId, storageProviderId, dosrId, ready) {
  124. const tx = ready
  125. ? this.base.api.tx.dataObjectStorageRegistry.setRelationshipReady(storageProviderId, dosrId)
  126. : this.base.api.tx.dataObjectStorageRegistry.unsetRelationshipReady(storageProviderId, dosrId)
  127. return this.base.signAndSend(providerAccountId, tx)
  128. }
  129. /*
  130. * Returns array of all the content ids in storage
  131. */
  132. async getKnownContentIds() {
  133. const keys = await this.base.api.query.dataDirectory.dataByContentId.keys()
  134. return keys.map(({ args: [contentId] }) => contentId)
  135. }
  136. /*
  137. * Returns array of all content ids in storage where liaison judgement was Accepted
  138. */
  139. getAcceptedContentIds() {
  140. if (!this._cachedEntries) {
  141. return []
  142. }
  143. return this._cachedEntries
  144. .filter(([, dataObject]) => dataObject.liaison_judgement.type === 'Accepted')
  145. .map(
  146. ([
  147. {
  148. args: [contentId],
  149. },
  150. ]) => contentId
  151. )
  152. }
  153. /*
  154. * Returns array of all ipfs hashes in storage where liaison judgement was Accepted
  155. */
  156. getAcceptedIpfsHashes() {
  157. if (!this._cachedEntries) {
  158. return []
  159. }
  160. const hashes = new Map()
  161. this._cachedEntries
  162. .filter(([, dataObject]) => dataObject.liaison_judgement.type === 'Accepted')
  163. .forEach(([, dataObject]) => hashes.set(dataObject.ipfs_content_id.toString()))
  164. return Array.from(hashes.keys())
  165. }
  166. /*
  167. * Fetch and cache all data objects
  168. */
  169. async fetchDataObjects() {
  170. this._cachedEntries = await this.base.api.query.dataDirectory.dataByContentId.entries()
  171. this._idMappings = new Map()
  172. this._cachedEntries.forEach(([{ args: [contentId] }, dataObject]) =>
  173. this._idMappings.set(contentId.encode(), dataObject.ipfs_content_id.toString())
  174. )
  175. }
  176. resolveContentIdToIpfsHash(contentId) {
  177. if (!this._idMappings) return null
  178. return this._idMappings.get(contentId)
  179. }
  180. }
  181. module.exports = {
  182. AssetsApi,
  183. }