cli.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. #!/usr/bin/env node
  2. const { RuntimeApi } = require('@joystream/storage-runtime-api')
  3. const { encodeAddress } = require('@polkadot/keyring')
  4. const { DiscoveryClient } = 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.dataByContentId(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. const discoveryClient = new DiscoveryClient({ api: runtime })
  108. // Resolve IPNS identities of providers
  109. console.log('\nResolving live provider API Endpoints...')
  110. const endpoints = await Promise.all(
  111. providersStatuses.map(async ({ providerId }) => {
  112. try {
  113. const serviceInfo = await discoveryClient.discoverOverJoystreamDiscoveryService(providerId)
  114. if (serviceInfo === null) {
  115. console.log(`provider ${providerId} has not published service information`)
  116. return { providerId, endpoint: null }
  117. }
  118. const info = JSON.parse(serviceInfo.serialized)
  119. console.log(`${providerId} -> ${info.asset.endpoint}`)
  120. return { providerId, endpoint: info.asset.endpoint }
  121. } catch (err) {
  122. console.log('resolve failed for id', providerId, err.message)
  123. return { providerId, endpoint: null }
  124. }
  125. })
  126. )
  127. console.log('\nChecking API Endpoints are online')
  128. await Promise.all(
  129. endpoints.map(async (provider) => {
  130. if (!provider.endpoint) {
  131. console.log('skipping', provider.address)
  132. return
  133. }
  134. const swaggerUrl = `${stripEndingSlash(provider.endpoint)}/swagger.json`
  135. let error
  136. try {
  137. await axios.get(swaggerUrl)
  138. // maybe print out api version information to detect which version of colossus is running?
  139. // or add anothe api endpoint for diagnostics information
  140. } catch (err) {
  141. error = err
  142. }
  143. console.log(`${provider.endpoint} - ${error ? error.message : 'OK'}`)
  144. })
  145. )
  146. const knownContentIds = await runtime.assets.getKnownContentIds()
  147. console.log(`\nData Directory has ${knownContentIds.length} assets`)
  148. // Check which providers are reporting a ready relationship for each asset
  149. await Promise.all(
  150. knownContentIds.map(async (contentId) => {
  151. const [relationshipsCount, judgement] = await assetRelationshipState(api, contentId, storageProviders)
  152. console.log(
  153. `${encodeAddress(contentId)} replication ${relationshipsCount}/${storageProviders.length} - ${judgement}`
  154. )
  155. })
  156. )
  157. // interesting disconnect doesn't work unless an explicit provider was created
  158. // for underlying api instance
  159. // We no longer need a connection to the chain
  160. api.disconnect()
  161. console.log(`\nChecking available assets on providers (this can take some time)...`)
  162. endpoints.forEach(async ({ providerId, endpoint }) => {
  163. if (!endpoint) {
  164. return
  165. }
  166. const total = knownContentIds.length
  167. const { found } = await countContentAvailability(knownContentIds, endpoint)
  168. console.log(`provider ${providerId}: has ${found} out of ${total}`)
  169. })
  170. }
  171. main()