common.ts 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. import { SubstrateEvent, SubstrateExtrinsic, ExtrinsicArg } from '@dzlzv/hydra-common'
  2. import { DatabaseManager } from '@dzlzv/hydra-db-utils'
  3. import { u64, Bytes } from '@polkadot/types/primitive'
  4. import { fixBlockTimestamp } from './eventFix'
  5. // Asset
  6. import { DataObjectOwner, DataObject, LiaisonJudgement, Network, NextEntityId } from 'query-node'
  7. import { ContentParameters } from '@joystream/types/augment'
  8. import { ContentParameters as Custom_ContentParameters } from '@joystream/types/storage'
  9. import { registry } from '@joystream/types'
  10. const currentNetwork = Network.BABYLON
  11. /*
  12. Reports that insurmountable inconsistent state has been encountered and throws an exception.
  13. */
  14. export function inconsistentState(extraInfo: string, data?: unknown): never {
  15. const errorMessage = 'Inconsistent state: ' + extraInfo
  16. // log error
  17. logger.error(errorMessage, data)
  18. throw new Error(errorMessage)
  19. }
  20. /*
  21. Reports that metadata inserted by the user are not entirely valid, but the problem can be overcome.
  22. */
  23. export function invalidMetadata(extraInfo: string, data?: unknown): void {
  24. const errorMessage = 'Invalid metadata: ' + extraInfo
  25. // log error
  26. logger.info(errorMessage, data)
  27. }
  28. /*
  29. Creates a predictable and unique ID for the given content.
  30. */
  31. export async function getNextId(db: DatabaseManager): Promise<string> {
  32. // load or create record
  33. const existingRecord = (await db.get(NextEntityId, {})) || new NextEntityId({ id: '0', nextId: 1 })
  34. // remember id
  35. const entityId = existingRecord.nextId
  36. // increment id
  37. existingRecord.nextId = existingRecord.nextId + 1
  38. // save record
  39. await db.save<NextEntityId>(existingRecord)
  40. return entityId.toString()
  41. }
  42. /*
  43. Prepares data object from content parameters.
  44. */
  45. export async function prepareDataObject(
  46. db: DatabaseManager,
  47. contentParameters: ContentParameters,
  48. event: SubstrateEvent,
  49. owner: typeof DataObjectOwner
  50. ): Promise<DataObject> {
  51. // convert generic content parameters coming from processor to custom Joystream data type
  52. const customContentParameters = new Custom_ContentParameters(registry, contentParameters.toJSON() as any)
  53. const dataObject = new DataObject({
  54. id: await getNextId(db),
  55. owner,
  56. createdInBlock: event.blockNumber,
  57. typeId: contentParameters.type_id.toNumber(),
  58. size: customContentParameters.size_in_bytes.toNumber(),
  59. liaisonJudgement: LiaisonJudgement.PENDING, // judgement is pending at start; liaison id is set when content is accepted/rejected
  60. ipfsContentId: convertBytesToString(contentParameters.ipfs_content_id),
  61. joystreamContentId: customContentParameters.content_id.encode(),
  62. createdAt: new Date(fixBlockTimestamp(event.blockTimestamp).toNumber()),
  63. updatedAt: new Date(fixBlockTimestamp(event.blockTimestamp).toNumber()),
  64. createdById: '1',
  65. updatedById: '1',
  66. })
  67. return dataObject
  68. }
  69. /// ///////////////// Sudo extrinsic calls ///////////////////////////////////////
  70. // soft-peg interface for typegen-generated `*Call` types
  71. export interface IGenericExtrinsicObject<T> {
  72. readonly extrinsic: SubstrateExtrinsic
  73. readonly expectedArgTypes: string[]
  74. args: T
  75. }
  76. // arguments for calling extrinsic as sudo
  77. export interface ISudoCallArgs<T> extends ExtrinsicArg {
  78. args: T
  79. callIndex: string
  80. }
  81. /*
  82. Extracts extrinsic arguments from the Substrate event. Supports both direct extrinsic calls and sudo calls.
  83. */
  84. export function extractExtrinsicArgs<DataParams, EventObject extends IGenericExtrinsicObject<DataParams>>(
  85. rawEvent: SubstrateEvent,
  86. callFactory: new (event: SubstrateEvent) => EventObject,
  87. // in ideal world this parameter would not be needed, but there is no way to associate parameters
  88. // used in sudo to extrinsic parameters without it
  89. argsIndeces: Record<keyof DataParams, number>
  90. ): EventObject['args'] {
  91. // this is equal to DataParams but only this notation works properly
  92. // escape when extrinsic info is not available
  93. if (!rawEvent.extrinsic) {
  94. throw new Error('Invalid event - no extrinsic set') // this should never happen
  95. }
  96. // regural extrinsic call?
  97. if (rawEvent.extrinsic.section !== 'sudo') {
  98. // eslint-disable-next-line new-cap
  99. return new callFactory(rawEvent).args
  100. }
  101. // sudo extrinsic call
  102. const callArgs = extractSudoCallParameters<DataParams>(rawEvent)
  103. // convert naming convention (underscore_names to camelCase)
  104. const clearArgs = Object.keys(callArgs.args).reduce((acc, key) => {
  105. const formattedName = key.replace(/_([a-z])/g, (tmp) => tmp[1].toUpperCase())
  106. acc[formattedName] = callArgs.args[key]
  107. return acc
  108. }, {} as DataParams)
  109. // prepare partial event object
  110. const partialEvent = {
  111. extrinsic: ({
  112. args: Object.keys(argsIndeces).reduce((acc, key) => {
  113. acc[argsIndeces[key]] = {
  114. value: clearArgs[key],
  115. }
  116. return acc
  117. }, [] as unknown[]),
  118. } as unknown) as SubstrateExtrinsic,
  119. } as SubstrateEvent
  120. // create event object and extract processed args
  121. // eslint-disable-next-line new-cap
  122. const finalArgs = new callFactory(partialEvent).args
  123. return finalArgs
  124. }
  125. /*
  126. Extracts extrinsic call parameters used inside of sudo call.
  127. */
  128. export function extractSudoCallParameters<DataParams>(rawEvent: SubstrateEvent): ISudoCallArgs<DataParams> {
  129. if (!rawEvent.extrinsic) {
  130. throw new Error('Invalid event - no extrinsic set') // this should never happen
  131. }
  132. // see Substrate's sudo frame for more info about sudo extrinsics and `call` argument index
  133. const argIndex =
  134. false ||
  135. (rawEvent.extrinsic.method === 'sudoAs' && 1) || // who, *call*
  136. (rawEvent.extrinsic.method === 'sudo' && 0) || // *call*
  137. (rawEvent.extrinsic.method === 'sudoUncheckedWeight' && 0) // *call*, _weight
  138. // ensure `call` argument was found
  139. if (argIndex === false) {
  140. // this could possibly happen in sometime in future if new sudo options are introduced in Substrate
  141. throw new Error('Not implemented situation with sudo')
  142. }
  143. // typecast call arguments
  144. const callArgs = (rawEvent.extrinsic.args[argIndex].value as unknown) as ISudoCallArgs<DataParams>
  145. return callArgs
  146. }
  147. /// ///////////////// Logger /////////////////////////////////////////////////////
  148. /*
  149. Simple logger enabling error and informational reporting.
  150. `Logger` class will not be needed in the future when Hydra v3 will be released.
  151. Hydra will provide logger instance and relevant code using `Logger` should be refactored.
  152. */
  153. class Logger {
  154. /*
  155. Log significant event.
  156. */
  157. info(message: string, data?: unknown) {
  158. console.log(message, data)
  159. }
  160. /*
  161. Log significant error.
  162. */
  163. error(message: string, data?: unknown) {
  164. console.error(message, data)
  165. }
  166. }
  167. export const logger = new Logger()
  168. /*
  169. Helper for converting Bytes type to string
  170. */
  171. export function convertBytesToString(b: Bytes | null): string {
  172. if (!b) {
  173. return ''
  174. }
  175. const text = Buffer.from(b.toU8a(true)).toString()
  176. // prevent utf-8 null character
  177. // eslint-disable-next-line no-control-regex
  178. const result = text.replace(/\u0000/g, '')
  179. return result
  180. }