cli.js 6.3 KB

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