storage.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. /*
  2. eslint-disable @typescript-eslint/naming-convention
  3. */
  4. import { DatabaseManager, EventContext, StoreContext } from '@joystream/hydra-common'
  5. import { Storage } from './generated/types/storage'
  6. import {
  7. StorageBag,
  8. StorageBagOwner,
  9. StorageBagOwnerChannel,
  10. StorageBagOwnerCouncil,
  11. StorageBagOwnerMember,
  12. StorageBagOwnerWorkingGroup,
  13. StorageBucket,
  14. StorageBucketOperatorStatusActive,
  15. StorageBucketOperatorStatusInvited,
  16. StorageBucketOperatorStatusMissing,
  17. StorageDataObject,
  18. StorageSystemParameters,
  19. } from 'query-node/dist/model'
  20. import BN from 'bn.js'
  21. import { getById, getWorkingGroupModuleName } from './common'
  22. import { BTreeSet } from '@polkadot/types'
  23. import { DataObjectCreationParameters } from '@joystream/types/storage'
  24. import { registry } from '@joystream/types'
  25. import { In } from 'typeorm'
  26. import _ from 'lodash'
  27. import { DataObjectId, BagId, DynamicBagId, StaticBagId } from '@joystream/types/augment/all'
  28. async function getDataObjectsInBag(
  29. store: DatabaseManager,
  30. bagId: BagId,
  31. dataObjectIds: BTreeSet<DataObjectId>
  32. ): Promise<StorageDataObject[]> {
  33. const dataObjects = await store.getMany(StorageDataObject, {
  34. where: {
  35. id: In(Array.from(dataObjectIds).map((id) => id.toString())),
  36. storageBag: { id: getBagId(bagId) },
  37. },
  38. })
  39. if (dataObjects.length !== Array.from(dataObjectIds).length) {
  40. throw new Error(
  41. `Missing data objects: ${_.difference(
  42. Array.from(dataObjectIds).map((id) => id.toString()),
  43. dataObjects.map((o) => o.id)
  44. )} in bag ${getBagId(bagId)}`
  45. )
  46. }
  47. return dataObjects
  48. }
  49. function getStaticBagOwner(bagId: StaticBagId): typeof StorageBagOwner {
  50. if (bagId.isCouncil) {
  51. return new StorageBagOwnerCouncil()
  52. } else if (bagId.isWorkingGroup) {
  53. const owner = new StorageBagOwnerWorkingGroup()
  54. owner.workingGroupId = getWorkingGroupModuleName(bagId.asWorkingGroup)
  55. return owner
  56. } else {
  57. throw new Error(`Unexpected static bag type: ${bagId.type}`)
  58. }
  59. }
  60. function getDynamicBagOwner(bagId: DynamicBagId) {
  61. if (bagId.isChannel) {
  62. const owner = new StorageBagOwnerChannel()
  63. owner.channelId = bagId.asChannel.toNumber()
  64. return owner
  65. } else if (bagId.isMember) {
  66. const owner = new StorageBagOwnerMember()
  67. owner.memberId = bagId.asMember.toNumber()
  68. return owner
  69. } else {
  70. throw new Error(`Unexpected dynamic bag type: ${bagId.type}`)
  71. }
  72. }
  73. function getStaticBagId(bagId: StaticBagId): string {
  74. if (bagId.isCouncil) {
  75. return `CO`
  76. } else if (bagId.isWorkingGroup) {
  77. return `WG-${bagId.asWorkingGroup.type}`
  78. } else {
  79. throw new Error(`Unexpected static bag type: ${bagId.type}`)
  80. }
  81. }
  82. function getDynamicBagId(bagId: DynamicBagId): string {
  83. if (bagId.isChannel) {
  84. return `CH-${bagId.asChannel.toString()}`
  85. } else if (bagId.isMember) {
  86. return `M-${bagId.asMember.toString()}`
  87. } else {
  88. throw new Error(`Unexpected dynamic bag type: ${bagId.type}`)
  89. }
  90. }
  91. function getBagId(bagId: BagId) {
  92. return bagId.isStatic ? getStaticBagId(bagId.asStatic) : getDynamicBagId(bagId.asDynamic)
  93. }
  94. async function getDynamicBag(
  95. store: DatabaseManager,
  96. bagId: DynamicBagId,
  97. relations?: ('storedBy' | 'objects')[]
  98. ): Promise<StorageBag> {
  99. return getById(store, StorageBag, getDynamicBagId(bagId), relations)
  100. }
  101. async function getStaticBag(
  102. store: DatabaseManager,
  103. bagId: StaticBagId,
  104. relations?: ('storedBy' | 'objects')[]
  105. ): Promise<StorageBag> {
  106. const id = getStaticBagId(bagId)
  107. const bag = await store.get(StorageBag, { where: { id }, relations })
  108. if (!bag) {
  109. console.log(`Creating new static bag: ${id}`)
  110. const newBag = new StorageBag({
  111. id,
  112. owner: getStaticBagOwner(bagId),
  113. })
  114. await store.save<StorageBag>(newBag)
  115. return newBag
  116. }
  117. return bag
  118. }
  119. async function getBag(store: DatabaseManager, bagId: BagId, relations?: 'storedBy'[]): Promise<StorageBag> {
  120. return bagId.isStatic
  121. ? getStaticBag(store, bagId.asStatic, relations)
  122. : getDynamicBag(store, bagId.asDynamic, relations)
  123. }
  124. // BUCKETS
  125. export async function storage_StorageBucketCreated({ event, store }: EventContext & StoreContext): Promise<void> {
  126. const [
  127. bucketId,
  128. invitedWorkerId,
  129. acceptingNewBags,
  130. dataObjectSizeLimit,
  131. dataObjectCountLimit,
  132. ] = new Storage.StorageBucketCreatedEvent(event).params
  133. const storageBucket = new StorageBucket({
  134. id: bucketId.toString(),
  135. acceptingNewBags: acceptingNewBags.isTrue,
  136. dataObjectCountLimit: new BN(dataObjectCountLimit.toString()),
  137. dataObjectsSizeLimit: new BN(dataObjectSizeLimit.toString()),
  138. })
  139. if (invitedWorkerId.isSome) {
  140. const operatorStatus = new StorageBucketOperatorStatusInvited()
  141. operatorStatus.workerId = invitedWorkerId.unwrap().toNumber()
  142. storageBucket.operatorStatus = operatorStatus
  143. } else {
  144. storageBucket.operatorStatus = new StorageBucketOperatorStatusMissing()
  145. }
  146. await store.save<StorageBucket>(storageBucket)
  147. }
  148. export async function storage_StorageOperatorMetadataSet({ event, store }: EventContext & StoreContext): Promise<void> {
  149. const [bucketId, , metadataBytes] = new Storage.StorageOperatorMetadataSetEvent(event).params
  150. const storageBucket = await getById(store, StorageBucket, bucketId.toString())
  151. storageBucket.operatorMetadata = Buffer.from(metadataBytes.toU8a(true))
  152. await store.save<StorageBucket>(storageBucket)
  153. }
  154. export async function storage_StorageBucketStatusUpdated({ event, store }: EventContext & StoreContext): Promise<void> {
  155. const [bucketId, , acceptingNewBags] = new Storage.StorageBucketStatusUpdatedEvent(event).params
  156. const storageBucket = await getById(store, StorageBucket, bucketId.toString())
  157. storageBucket.acceptingNewBags = acceptingNewBags.isTrue
  158. await store.save<StorageBucket>(storageBucket)
  159. }
  160. export async function storage_StorageBucketInvitationAccepted({
  161. event,
  162. store,
  163. }: EventContext & StoreContext): Promise<void> {
  164. const [bucketId, workerId] = new Storage.StorageBucketInvitationAcceptedEvent(event).params
  165. const storageBucket = await getById(store, StorageBucket, bucketId.toString())
  166. const operatorStatus = new StorageBucketOperatorStatusActive()
  167. operatorStatus.workerId = workerId.toNumber()
  168. storageBucket.operatorStatus = operatorStatus
  169. await store.save<StorageBucket>(storageBucket)
  170. }
  171. export async function storage_StorageBucketInvitationCancelled({
  172. event,
  173. store,
  174. }: EventContext & StoreContext): Promise<void> {
  175. const [bucketId] = new Storage.StorageBucketInvitationCancelledEvent(event).params
  176. const storageBucket = await getById(store, StorageBucket, bucketId.toString())
  177. const operatorStatus = new StorageBucketOperatorStatusMissing()
  178. storageBucket.operatorStatus = operatorStatus
  179. await store.save<StorageBucket>(storageBucket)
  180. }
  181. export async function storage_StorageBucketOperatorInvited({
  182. event,
  183. store,
  184. }: EventContext & StoreContext): Promise<void> {
  185. const [bucketId, workerId] = new Storage.StorageBucketOperatorInvitedEvent(event).params
  186. const storageBucket = await getById(store, StorageBucket, bucketId.toString())
  187. const operatorStatus = new StorageBucketOperatorStatusInvited()
  188. operatorStatus.workerId = workerId.toNumber()
  189. storageBucket.operatorStatus = operatorStatus
  190. await store.save<StorageBucket>(storageBucket)
  191. }
  192. export async function storage_StorageBucketOperatorRemoved({
  193. event,
  194. store,
  195. }: EventContext & StoreContext): Promise<void> {
  196. const [bucketId] = new Storage.StorageBucketInvitationCancelledEvent(event).params
  197. const storageBucket = await getById(store, StorageBucket, bucketId.toString())
  198. const operatorStatus = new StorageBucketOperatorStatusMissing()
  199. storageBucket.operatorStatus = operatorStatus
  200. await store.save<StorageBucket>(storageBucket)
  201. }
  202. export async function storage_StorageBucketsUpdatedForBag({
  203. event,
  204. store,
  205. }: EventContext & StoreContext): Promise<void> {
  206. const [bagId, addedBucketsIds, removedBucketsIds] = new Storage.StorageBucketsUpdatedForBagEvent(event).params
  207. const storageBag = await getBag(store, bagId, ['storedBy'])
  208. storageBag.storedBy = (storageBag.storedBy || [])
  209. .filter((b) => !Array.from(removedBucketsIds).some((id) => id.eq(b.id)))
  210. .concat(Array.from(addedBucketsIds).map((id) => new StorageBucket({ id: id.toString() })))
  211. await store.save<StorageBag>(storageBag)
  212. }
  213. export async function storage_StorageBucketDeleted({ event, store }: EventContext & StoreContext): Promise<void> {
  214. const [bucketId] = new Storage.StorageBucketDeletedEvent(event).params
  215. // TODO: Delete or just change status?
  216. // TODO: Cascade remove on db level?
  217. // We shouldn't have to worry about deleting DataObjects, since this is already enforced by the runtime
  218. const storageBucket = await getById(store, StorageBucket, bucketId.toString(), ['storedBags'])
  219. await Promise.all((storageBucket.storedBags || []).map((b) => store.remove<StorageBag>(b)))
  220. await store.remove<StorageBucket>(storageBucket)
  221. }
  222. // DYNAMIC BAGS
  223. export async function storage_DynamicBagCreated({ event, store }: EventContext & StoreContext): Promise<void> {
  224. const [bagId] = new Storage.DynamicBagCreatedEvent(event).params
  225. const storageBag = new StorageBag({
  226. id: getDynamicBagId(bagId),
  227. owner: getDynamicBagOwner(bagId),
  228. })
  229. await store.save<StorageBag>(storageBag)
  230. }
  231. export async function storage_DynamicBagDeleted({ event, store }: EventContext & StoreContext): Promise<void> {
  232. const [, bagId] = new Storage.DynamicBagDeletedEvent(event).params
  233. // TODO: Delete or just change status?
  234. // TODO: Cascade remove on db level?
  235. const storageBag = await getDynamicBag(store, bagId, ['objects'])
  236. await Promise.all((storageBag.objects || []).map((o) => store.remove<StorageDataObject>(o)))
  237. await store.remove<StorageBag>(storageBag)
  238. }
  239. // DATA OBJECTS
  240. // Note: "Uploaded" here actually means "created" (the real upload happens later)
  241. export async function storage_DataObjectdUploaded({ event, store }: EventContext & StoreContext): Promise<void> {
  242. const [dataObjectIds, uploadParams] = new Storage.DataObjectdUploadedEvent(event).params
  243. const { bagId, authenticationKey, objectCreationList } = uploadParams
  244. const storageBag = await getBag(store, bagId)
  245. const dataObjects = dataObjectIds.map((objectId, i) => {
  246. const objectParams = new DataObjectCreationParameters(registry, objectCreationList[i].toJSON() as any)
  247. return new StorageDataObject({
  248. id: objectId.toString(),
  249. authenticationKey: authenticationKey.toString(),
  250. isAccepted: false,
  251. ipfsHash: objectParams.ipfsContentId.toString(),
  252. size: new BN(objectParams.getField('size').toString()),
  253. storageBag,
  254. })
  255. })
  256. await Promise.all(dataObjects.map((o) => store.save<StorageDataObject>(o)))
  257. }
  258. export async function storage_PendingDataObjectsAccepted({ event, store }: EventContext & StoreContext): Promise<void> {
  259. const [, , bagId, dataObjectIds] = new Storage.PendingDataObjectsAcceptedEvent(event).params
  260. const dataObjects = await getDataObjectsInBag(store, bagId, dataObjectIds)
  261. await Promise.all(
  262. dataObjects.map(async (dataObject) => {
  263. dataObject.isAccepted = true
  264. // TODO: Do we still want other storage providers to accept it? How long should the key be valid?
  265. // dataObject.authenticationKey = null as any
  266. await store.save<StorageDataObject>(dataObject)
  267. })
  268. )
  269. }
  270. export async function storage_DataObjectsMoved({ event, store }: EventContext & StoreContext): Promise<void> {
  271. const [srcBagId, destBagId, dataObjectIds] = new Storage.DataObjectsMovedEvent(event).params
  272. const dataObjects = await getDataObjectsInBag(store, srcBagId, dataObjectIds)
  273. const destBag = await getBag(store, destBagId)
  274. await Promise.all(
  275. dataObjects.map(async (dataObject) => {
  276. dataObject.storageBag = destBag
  277. await store.save<StorageDataObject>(dataObject)
  278. })
  279. )
  280. }
  281. export async function storage_DataObjectsDeleted({ event, store }: EventContext & StoreContext): Promise<void> {
  282. const [, bagId, dataObjectIds] = new Storage.DataObjectsDeletedEvent(event).params
  283. const dataObjects = await getDataObjectsInBag(store, bagId, dataObjectIds)
  284. // TODO: Delete them or just change status?
  285. // (may not be so optimal if we expect a large amount of data objects)
  286. await Promise.all(dataObjects.map((o) => store.remove<StorageDataObject>(o)))
  287. }
  288. // BLACKLIST
  289. export async function storage_UpdateBlacklist({ event, store }: EventContext & StoreContext): Promise<void> {
  290. const [removedContentIds, addedContentIds] = new Storage.UpdateBlacklistEvent(event).params
  291. const storageSystem = await store.get(StorageSystemParameters, {})
  292. if (!storageSystem) {
  293. throw new Error('StorageSystemParameters entity not found!')
  294. }
  295. storageSystem.blacklist = storageSystem.blacklist
  296. .filter((cid) => !Array.from(removedContentIds).some((id) => id.eq(cid)))
  297. .concat(Array.from(addedContentIds).map((id) => id.toString()))
  298. await store.save<StorageSystemParameters>(storageSystem)
  299. }
  300. export async function storage_StorageBucketVoucherLimitsSet({
  301. event,
  302. store,
  303. }: EventContext & StoreContext): Promise<void> {
  304. // To be implemented
  305. }
  306. export async function storage_UploadingBlockStatusUpdated({
  307. event,
  308. store,
  309. }: EventContext & StoreContext): Promise<void> {
  310. // To be implemented
  311. }
  312. export async function storage_DataObjectPerMegabyteFeeUpdated({
  313. event,
  314. store,
  315. }: EventContext & StoreContext): Promise<void> {
  316. // To be implemented
  317. }
  318. export async function storage_StorageBucketsPerBagLimitUpdated({
  319. event,
  320. store,
  321. }: EventContext & StoreContext): Promise<void> {
  322. // To be implemented
  323. }
  324. export async function storage_StorageBucketsVoucherMaxLimitsUpdated({
  325. event,
  326. store,
  327. }: EventContext & StoreContext): Promise<void> {
  328. // To be implemented
  329. }
  330. export async function storage_DeletionPrizeChanged({ event, store }: EventContext & StoreContext): Promise<void> {
  331. // To be implemented
  332. }
  333. export async function storage_VoucherChanged({ event, store }: EventContext & StoreContext): Promise<void> {
  334. // To be implemented
  335. }
  336. export async function storage_NumberOfStorageBucketsInDynamicBagCreationPolicyUpdated({
  337. event,
  338. store,
  339. }: EventContext & StoreContext): Promise<void> {
  340. // To be implemented
  341. }