123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311 |
- 'use strict'
- const debug = require('debug')('joystream:runtime:base')
- const { registerJoystreamTypes } = require('@joystream/types')
- const { ApiPromise, WsProvider } = require('@polkadot/api')
- const { IdentitiesApi } = require('@joystream/storage-runtime-api/identities')
- const { BalancesApi } = require('@joystream/storage-runtime-api/balances')
- const { WorkersApi } = require('@joystream/storage-runtime-api/workers')
- const { AssetsApi } = require('@joystream/storage-runtime-api/assets')
- const { DiscoveryApi } = require('@joystream/storage-runtime-api/discovery')
- const { SystemApi } = require('@joystream/storage-runtime-api/system')
- const AsyncLock = require('async-lock')
- const { newExternallyControlledPromise } = require('@joystream/storage-utils/externalPromise')
- class RuntimeApi {
- static async create(options) {
- const runtimeApi = new RuntimeApi()
- await runtimeApi.init(options || {})
- return runtimeApi
- }
- async init(options) {
- debug('Init')
- options = options || {}
-
- registerJoystreamTypes()
- const provider = new WsProvider(options.provider_url || 'ws://localhost:9944')
-
- this.api = await ApiPromise.create({ provider })
- this.asyncLock = new AsyncLock()
-
- this.nonces = {}
-
- this.storageProviderId = parseInt(options.storageProviderId)
-
- this.identities = await IdentitiesApi.create(this, {
- account_file: options.account_file,
- passphrase: options.passphrase,
- canPromptForPassphrase: options.canPromptForPassphrase,
- })
- this.balances = await BalancesApi.create(this)
- this.workers = await WorkersApi.create(this)
- this.assets = await AssetsApi.create(this)
- this.discovery = await DiscoveryApi.create(this)
- this.system = await SystemApi.create(this)
- }
- disconnect() {
- this.api.disconnect()
- }
- executeWithAccountLock(accountId, func) {
- return this.asyncLock.acquire(`${accountId}`, func)
- }
-
- async waitForEvent(module, name) {
- return this.waitForEvents([[module, name]])
- }
- static matchingEvents(subscribed, events) {
- debug(`Number of events: ${events.length} subscribed to ${subscribed}`)
- const filtered = events.filter((record) => {
- const { event, phase } = record
-
- debug(`\t${event.section}:${event.method}:: (phase=${phase.toString()})`)
- debug(`\t\t${event.meta.documentation.toString()}`)
-
- const matching = subscribed.filter((value) => {
- return event.section === value[0] && event.method === value[1]
- })
- return matching.length > 0
- })
- debug(`Filtered: ${filtered.length}`)
- const mapped = filtered.map((record) => {
- const { event } = record
- const types = event.typeDef
-
- const payload = {}
- event.data.forEach((data, index) => {
- debug(`\t\t\t${types[index].type}: ${data.toString()}`)
- payload[types[index].type] = data
- })
- const fullName = `${event.section}.${event.method}`
- return [fullName, payload]
- })
- debug('Mapped', mapped)
- return mapped
- }
-
- async waitForEvents(subscribed) {
- return new Promise((resolve) => {
- this.api.query.system.events((events) => {
- const matches = RuntimeApi.matchingEvents(subscribed, events)
- if (matches && matches.length) {
- resolve(matches)
- }
- })
- })
- }
-
- async signAndSend(accountId, tx, attempts, subscribed, callback) {
- accountId = this.identities.keyring.encodeAddress(accountId)
-
- const fromKey = this.identities.keyring.getPair(accountId)
- if (fromKey.isLocked) {
- throw new Error('Must unlock key before using it to sign!')
- }
- const finalizedPromise = newExternallyControlledPromise()
- await this.executeWithAccountLock(accountId, async () => {
-
- let nonce = this.nonces[accountId]
- let incrementNonce = () => {
-
- incrementNonce = () => {
-
- }
- nonce = nonce.addn(1)
- this.nonces[accountId] = nonce
- }
-
- if (!nonce) {
-
-
-
- nonce = await this.api.query.system.accountNonce(accountId)
- debug(`Got nonce for ${accountId} from chain: ${nonce}`)
- }
- return new Promise((resolve, reject) => {
- debug('Signing and sending tx')
-
- const unsubscribe = tx
- .sign(fromKey, { nonce })
- .send(({ events = [], status }) => {
- debug(`TX status: ${status.type}`)
-
-
- try {
- if (subscribed && callback) {
- const matched = RuntimeApi.matchingEvents(subscribed, events)
- debug('Matching events:', matched)
- if (matched.length) {
- callback(matched)
- }
- }
- } catch (err) {
- debug(`Error handling events ${err.stack}`)
- }
-
-
- if (status.isReady) {
- debug('TX Ready.')
- incrementNonce()
- resolve(unsubscribe)
- } else if (status.isBroadcast) {
- debug('TX Broadcast.')
- incrementNonce()
- resolve(unsubscribe)
- } else if (status.isFinalized) {
- debug('TX Finalized.')
- finalizedPromise.resolve(status)
- } else if (status.isFuture) {
-
-
-
- debug('TX Future!')
-
- delete this.nonces[accountId]
- const err = new Error('transaction nonce set in future')
- finalizedPromise.reject(err)
- reject(err)
- }
-
- })
- .catch((err) => {
-
-
-
-
- if (err) {
- const errstr = err.toString()
-
-
- if (
- errstr.indexOf('Error: 1014:') < 0 &&
- errstr.indexOf('Error: 1010:') < 0
- ) {
-
-
- debug('TX error', err)
- } else {
-
- delete this.nonces[accountId]
- }
- }
- finalizedPromise.reject(err)
-
- reject(err)
- })
- })
- })
-
-
-
-
- return finalizedPromise.promise
- }
-
- async signAndSendThenGetEventResult(senderAccountId, tx, { eventModule, eventName, eventProperty }) {
-
- const subscribed = [[eventModule, eventName]]
-
-
- return new Promise(async (resolve, reject) => {
- try {
- await this.signAndSend(senderAccountId, tx, 1, subscribed, (events) => {
- events.forEach((event) => {
-
-
- resolve(event[1][eventProperty])
- })
- })
- } catch (err) {
- reject(err)
- }
- })
- }
- }
- module.exports = {
- RuntimeApi,
- }
|