cli.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. #!/usr/bin/env node
  2. const { RuntimeApi } = require('@joystream/storage-runtime-api')
  3. const { encodeAddress } = require('@polkadot/keyring')
  4. const { discover } = require('@joystream/service-discovery')
  5. const axios = require('axios')
  6. const stripEndingSlash = require('@joystream/storage-utils/stripEndingSlash')
  7. function mapInfoToStatus(providers, currentHeight) {
  8. return providers.map(({ providerId, info }) => {
  9. if (info) {
  10. return {
  11. providerId,
  12. identity: info.identity.toString(),
  13. expiresIn: info.expires_at.sub(currentHeight).toNumber(),
  14. expired: currentHeight.gte(info.expires_at),
  15. }
  16. }
  17. return {
  18. providerId,
  19. identity: null,
  20. status: 'down',
  21. }
  22. })
  23. }
  24. function makeAssetUrl(contentId, source) {
  25. source = stripEndingSlash(source)
  26. return `${source}/asset/v0/${encodeAddress(contentId)}`
  27. }
  28. async function assetRelationshipState(api, contentId, providers) {
  29. const dataObject = await api.query.dataDirectory.dataObjectByContentId(contentId)
  30. const relationshipIds = await api.query.dataObjectStorageRegistry.relationshipsByContentId(contentId)
  31. // how many relationships associated with active providers and in ready state
  32. const activeRelationships = await Promise.all(
  33. relationshipIds.map(async (id) => {
  34. let relationship = await api.query.dataObjectStorageRegistry.relationships(id)
  35. relationship = relationship.unwrap()
  36. // only interested in ready relationships
  37. if (!relationship.ready) {
  38. return undefined
  39. }
  40. // Does the relationship belong to an active provider ?
  41. return providers.find((provider) => relationship.storage_provider.eq(provider))
  42. })
  43. )
  44. return [activeRelationships.filter((active) => active).length, dataObject.unwrap().liaison_judgement]
  45. }
  46. // HTTP HEAD with axios all known content ids on each provider
  47. async function countContentAvailability(contentIds, source) {
  48. const content = {}
  49. let found = 0
  50. let missing = 0
  51. for (let i = 0; i < contentIds.length; i++) {
  52. const assetUrl = makeAssetUrl(contentIds[i], source)
  53. try {
  54. const info = await axios.head(assetUrl)
  55. content[encodeAddress(contentIds[i])] = {
  56. type: info.headers['content-type'],
  57. bytes: info.headers['content-length'],
  58. }
  59. // TODO: cross check against dataobject size
  60. found++
  61. } catch (err) {
  62. missing++
  63. }
  64. }
  65. return { found, missing, content }
  66. }
  67. async function main() {
  68. const runtime = await RuntimeApi.create()
  69. const { api } = runtime
  70. // get current blockheight
  71. const currentHeader = await api.rpc.chain.getHeader()
  72. const currentHeight = currentHeader.number.toBn()
  73. // get all providers
  74. const { ids: storageProviders } = await runtime.workers.getAllProviders()
  75. console.log(`Found ${storageProviders.length} staked providers`)
  76. const storageProviderAccountInfos = await Promise.all(
  77. storageProviders.map(async (providerId) => {
  78. return {
  79. providerId,
  80. info: await runtime.discovery.getAccountInfo(providerId),
  81. }
  82. })
  83. )
  84. // providers that have updated their account info and published ipfs id
  85. // considered live if the record hasn't expired yet
  86. const liveProviders = storageProviderAccountInfos.filter(({ info }) => {
  87. return info && info.expires_at.gte(currentHeight)
  88. })
  89. const downProviders = storageProviderAccountInfos.filter(({ info }) => {
  90. return info === null
  91. })
  92. const expiredTtlProviders = storageProviderAccountInfos.filter(({ info }) => {
  93. return info && currentHeight.gte(info.expires_at)
  94. })
  95. const providersStatuses = mapInfoToStatus(liveProviders, currentHeight)
  96. console.log('\n== Live Providers\n', providersStatuses)
  97. const expiredProviderStatuses = mapInfoToStatus(expiredTtlProviders, currentHeight)
  98. console.log('\n== Expired Providers\n', expiredProviderStatuses)
  99. console.log(
  100. '\n== Down Providers!\n',
  101. downProviders.map((provider) => {
  102. return {
  103. providerId: provider.providerId,
  104. }
  105. })
  106. )
  107. // Resolve IPNS identities of providers
  108. console.log('\nResolving live provider API Endpoints...')
  109. const endpoints = await Promise.all(
  110. providersStatuses.map(async ({ providerId }) => {
  111. try {
  112. const serviceInfo = await discover.discoverOverJoystreamDiscoveryService(providerId, runtime)
  113. if (serviceInfo === null) {
  114. console.log(`provider ${providerId} has not published service information`)
  115. return { providerId, endpoint: null }
  116. }
  117. const info = JSON.parse(serviceInfo.serialized)
  118. console.log(`${providerId} -> ${info.asset.endpoint}`)
  119. return { providerId, endpoint: info.asset.endpoint }
  120. } catch (err) {
  121. console.log('resolve failed for id', providerId, err.message)
  122. return { providerId, endpoint: null }
  123. }
  124. })
  125. )
  126. console.log('\nChecking API Endpoints are online')
  127. await Promise.all(
  128. endpoints.map(async (provider) => {
  129. if (!provider.endpoint) {
  130. console.log('skipping', provider.address)
  131. return
  132. }
  133. const swaggerUrl = `${stripEndingSlash(provider.endpoint)}/swagger.json`
  134. let error
  135. try {
  136. await axios.get(swaggerUrl)
  137. // maybe print out api version information to detect which version of colossus is running?
  138. // or add anothe api endpoint for diagnostics information
  139. } catch (err) {
  140. error = err
  141. }
  142. console.log(`${provider.endpoint} - ${error ? error.message : 'OK'}`)
  143. })
  144. )
  145. const knownContentIds = await runtime.assets.getKnownContentIds()
  146. console.log(`\nData Directory has ${knownContentIds.length} assets`)
  147. // Check which providers are reporting a ready relationship for each asset
  148. await Promise.all(
  149. knownContentIds.map(async (contentId) => {
  150. const [relationshipsCount, judgement] = await assetRelationshipState(api, contentId, storageProviders)
  151. console.log(
  152. `${encodeAddress(contentId)} replication ${relationshipsCount}/${storageProviders.length} - ${judgement}`
  153. )
  154. })
  155. )
  156. // interesting disconnect doesn't work unless an explicit provider was created
  157. // for underlying api instance
  158. // We no longer need a connection to the chain
  159. api.disconnect()
  160. console.log(`\nChecking available assets on providers (this can take some time)...`)
  161. endpoints.forEach(async ({ providerId, endpoint }) => {
  162. if (!endpoint) {
  163. return
  164. }
  165. const total = knownContentIds.length
  166. const { found } = await countContentAvailability(knownContentIds, endpoint)
  167. console.log(`provider ${providerId}: has ${found} out of ${total}`)
  168. })
  169. }
  170. main()