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