Skip to content

Commit

Permalink
refactor!: do not auto-manage api connection (#622)
Browse files Browse the repository at this point in the history
* refactor: remove BlockchainApiConnection & autoconnect
* chore: clean up dependency tree
* feat: check if provider supports subscriptions
* chore: remove isRecoverableTxError
* chore: increase test coverage for Blockchain.ts
  • Loading branch information
rflechtner authored Sep 7, 2022
1 parent b2a5c99 commit c423069
Show file tree
Hide file tree
Showing 36 changed files with 331 additions and 397 deletions.
4 changes: 3 additions & 1 deletion packages/augment-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,14 @@
"bugs": "https://github.com/KILTprotocol/sdk-js/issues",
"homepage": "https://github.com/KILTprotocol/sdk-js#readme",
"devDependencies": {
"@polkadot/api": "^9.0.0",
"@polkadot/typegen": "^9.0.0",
"@types/websocket": "^1.0.0",
"glob": "^7.1.1",
"rimraf": "^3.0.2",
"ts-node": "^10.4.0",
"typescript": "^4.5.4",
"websocket": "^1.0.31"
"websocket": "^1.0.31",
"yargs": "^16.2.0"
}
}
1 change: 1 addition & 0 deletions packages/chain-helpers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"homepage": "https://github.com/KILTprotocol/sdk-js#readme",
"devDependencies": {
"@kiltprotocol/testing": "workspace:*",
"@polkadot/keyring": "^10.0.0",
"rimraf": "^3.0.2",
"typescript": "^4.5.4"
},
Expand Down
143 changes: 72 additions & 71 deletions packages/chain-helpers/src/blockchain/Blockchain.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,89 +9,29 @@
* @group unit/blockchain
*/

/* eslint-disable dot-notation */
import type { KeyringPair } from '@polkadot/keyring/types'
import { Keyring, ss58Format } from '@kiltprotocol/utils'
import Keyring from '@polkadot/keyring'

import { ApiMocks } from '@kiltprotocol/testing'
import { setConnection } from '../blockchainApiConnection/BlockchainApiConnection'
import { ConfigService } from '@kiltprotocol/config'
import type { KeyringPair } from '@kiltprotocol/types'
import { SDKErrors } from '@kiltprotocol/utils'

import {
IS_FINALIZED,
isRecoverableTxError,
IS_IN_BLOCK,
IS_READY,
parseSubscriptionOptions,
submitSignedTx,
signAndSubmitTx,
} from './Blockchain'

let api: any

beforeAll(() => {
beforeEach(() => {
api = ApiMocks.getMockedApi()
setConnection(Promise.resolve(api))
ConfigService.set({ api })
})

describe('Blockchain', () => {
describe('submitSignedTx', () => {
let alice: KeyringPair
let bob: KeyringPair

beforeAll(async () => {
const keyring = new Keyring({
type: 'ed25519',
ss58Format,
})
alice = keyring.createFromUri('//Alice')
bob = keyring.createFromUri('//Bob')
})

it('catches ERROR_TRANSACTION_USURPED and discovers as recoverable', async () => {
api.__setDefaultResult({ isUsurped: true })
const tx = api.tx.balances.transfer(bob.address, 100)
tx.signAsync(alice)
const error = await submitSignedTx(tx, parseSubscriptionOptions()).catch(
(e) => e
)
expect(isRecoverableTxError(error)).toBe(true)
}, 20_000)

it('catches priority error and discovers as recoverable', async () => {
api.__setDefaultResult()
const tx = api.tx.balances.transfer(bob.address, 100)
tx.signAsync(alice)
tx.send = jest.fn().mockRejectedValue(Error('1014: Priority is too low:'))
const error = await submitSignedTx(tx, parseSubscriptionOptions()).catch(
(e) => e
)
expect(isRecoverableTxError(error)).toBe(true)
}, 20_000)

it('catches Already Imported error and discovers as recoverable', async () => {
api.__setDefaultResult()
const tx = api.tx.balances.transfer(bob.address, 100)
tx.signAsync(alice)
tx.send = jest
.fn()
.mockRejectedValue(Error('Transaction Already Imported'))
const error = await submitSignedTx(tx, parseSubscriptionOptions()).catch(
(e) => e
)
expect(isRecoverableTxError(error)).toBe(true)
}, 20_000)

it('catches Outdated/Stale Tx error and discovers as recoverable', async () => {
api.__setDefaultResult()
const tx = api.tx.balances.transfer(bob.address, 100)
tx.signAsync(alice)
tx.send = jest
.fn()
.mockRejectedValue(
Error('1010: Invalid Transaction: Transaction is outdated')
)
const error = await submitSignedTx(tx, parseSubscriptionOptions()).catch(
(e) => e
)
expect(isRecoverableTxError(error)).toBe(true)
}, 20_000)
})

describe('parseSubscriptionOptions', () => {
it('takes incomplete SubscriptionPromiseOptions and sets default values where needed', async () => {
function testFunction() {
Expand Down Expand Up @@ -143,4 +83,65 @@ describe('Blockchain', () => {
})
})
})

describe('submitSignedTx', () => {
let pair: KeyringPair

beforeAll(async () => {
pair = new Keyring().addFromUri('//Alice')
})

it('allows waiting for finalization', async () => {
api.__setDefaultResult({ isFinalized: true })
const tx = api.tx.balances.transfer('abcdef', 50)
expect(
await signAndSubmitTx(tx, pair, { resolveOn: IS_FINALIZED })
).toHaveProperty('isFinalized', true)
})

it('allows waiting for in block', async () => {
api.__setDefaultResult({ isInBlock: true })
const tx = api.tx.balances.transfer('abcdef', 50)
expect(
await signAndSubmitTx(tx, pair, { resolveOn: IS_IN_BLOCK })
).toHaveProperty('isInBlock', true)
})

it('allows waiting for ready', async () => {
api.__setDefaultResult({ isReady: true })
const tx = api.tx.balances.transfer('abcdef', 50)
expect(
await signAndSubmitTx(tx, pair, { resolveOn: IS_READY })
).toHaveProperty('status.isReady', true)
})

it('rejects on error condition', async () => {
api.__setDefaultResult({ isInvalid: true })
const tx = api.tx.balances.transfer('abcdef', 50)
await expect(
signAndSubmitTx(tx, pair, { resolveOn: IS_FINALIZED })
).rejects.toHaveProperty('isError', true)
})

it('throws if subscriptions not supported', async () => {
// @ts-ignore
api.hasSubscriptions = false
const tx = api.tx.balances.transfer('abcdef', 50)
await expect(
signAndSubmitTx(tx, pair, { resolveOn: IS_FINALIZED })
).rejects.toThrow(SDKErrors.SubscriptionsNotSupportedError)
})

it('rejects if disconnected', async () => {
api.__setDefaultResult({ isReady: true })
api.once.mockImplementation((ev: string, callback: () => void) => {
// mock disconnect 500 ms after submission
if (ev === 'disconnected') setTimeout(callback, 500)
})
const tx = api.tx.balances.transfer('abcdef', 50)
await expect(
signAndSubmitTx(tx, pair, { resolveOn: IS_FINALIZED })
).rejects.toHaveProperty('internalError', expect.any(Error))
})
})
})
39 changes: 10 additions & 29 deletions packages/chain-helpers/src/blockchain/Blockchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,20 @@
* found in the LICENSE file in the root directory of this source tree.
*/

import { SubmittableResult } from '@polkadot/api'
import { AnyNumber } from '@polkadot/types/types'

import { ConfigService } from '@kiltprotocol/config'
import type {
ISubmittableResult,
KeyringPair,
SubmittableExtrinsic,
SubscriptionPromise,
} from '@kiltprotocol/types'
import { SubmittableResult } from '@polkadot/api'
import { AnyNumber } from '@polkadot/types/types'
import { SDKErrors } from '@kiltprotocol/utils'

import { ErrorHandler } from '../errorhandling/index.js'
import { makeSubscriptionPromise } from './SubscriptionPromise.js'
import { getConnectionOrConnect } from '../blockchainApiConnection/BlockchainApiConnection.js'

const log = ConfigService.LoggingFactory.getLogger('Blockchain')

Expand Down Expand Up @@ -125,6 +127,11 @@ export async function submitSignedTx(
tx: SubmittableExtrinsic,
opts?: Partial<SubscriptionPromise.Options>
): Promise<ISubmittableResult> {
const api = ConfigService.get('api')
if (!api.hasSubscriptions) {
throw new SDKErrors.SubscriptionsNotSupportedError()
}

log.info(`Submitting ${tx.method}`)
const options = parseSubscriptionOptions(opts)
const { promise, subscription } = makeSubscriptionPromise(options)
Expand All @@ -135,8 +142,6 @@ export async function submitSignedTx(
subscription(result)
})

const api = await getConnectionOrConnect()

function handleDisconnect(): void {
const result = new SubmittableResult({
events: latestResult?.events || [],
Expand All @@ -163,30 +168,6 @@ export async function submitSignedTx(

export const dispatchTx = submitSignedTx

/**
* Checks the TxError/TxStatus for issues that may be resolved via resigning.
*
* @param reason Polkadot API returned error or ISubmittableResult.
* @returns Whether or not this issue may be resolved via resigning.
*/
export function isRecoverableTxError(
reason: Error | ISubmittableResult
): boolean {
if (reason instanceof Error) {
return (
reason.message.includes(TxOutdated) ||
reason.message.includes(TxPriority) ||
reason.message.includes(TxDuplicate) ||
false
)
}
if (typeof reason === 'object' && typeof reason.status === 'object') {
const { status } = reason as ISubmittableResult
if (status.isUsurped) return true
}
return false
}

/**
* Signs and submits the SubmittableExtrinsic with optional resolution and rejection criteria.
*
Expand Down

This file was deleted.

8 changes: 0 additions & 8 deletions packages/chain-helpers/src/blockchainApiConnection/index.ts

This file was deleted.

1 change: 0 additions & 1 deletion packages/chain-helpers/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,4 @@
import '@kiltprotocol/augment-api'

export { ErrorHandler } from './errorhandling/index.js'
export { BlockchainApiConnection } from './blockchainApiConnection/index.js'
export { Blockchain, SubscriptionPromise } from './blockchain/index.js'
1 change: 1 addition & 0 deletions packages/config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
},
"dependencies": {
"@kiltprotocol/utils": "workspace:*",
"@polkadot/api": "^9.0.0",
"typescript-logging": "^0.6.4"
}
}
Loading

0 comments on commit c423069

Please sign in to comment.