cli.js 6.1 KB

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