Catch promise errors in subscription (#1138)
* Testcase for invalid tx (not caught) * Re-enable commented tests * Make the test pass * Cleanup tests, check for promise to exist
This commit is contained in:
@@ -52,7 +52,7 @@ export default async function test (): Promise<void> {
|
|||||||
// constants has actual value & metadata
|
// constants has actual value & metadata
|
||||||
console.log(
|
console.log(
|
||||||
api.consts.balances.creationFee.toHex(),
|
api.consts.balances.creationFee.toHex(),
|
||||||
api.consts.balances.creationFee.meta.documentation.map((s) => s.toString()).join('')
|
api.consts.balances.creationFee.meta.documentation.map((s): string => s.toString()).join('')
|
||||||
);
|
);
|
||||||
|
|
||||||
const transfer = api.tx.balances.transfer(keyring.bob.address, 12345);
|
const transfer = api.tx.balances.transfer(keyring.bob.address, 12345);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import Keyring from '@polkadot/keyring';
|
import Keyring from '@polkadot/keyring';
|
||||||
import testingPairs from '@polkadot/keyring/testingPairs';
|
import testingPairs from '@polkadot/keyring/testingPairs';
|
||||||
import WsProvider from '@polkadot/rpc-provider/ws';
|
import WsProvider from '@polkadot/rpc-provider/ws';
|
||||||
|
import { u8aToHex } from '@polkadot/util';
|
||||||
import { randomAsHex } from '@polkadot/util-crypto';
|
import { randomAsHex } from '@polkadot/util-crypto';
|
||||||
import { EventRecord, ExtrinsicEra, Hash, Header, Index, SignedBlock } from '@polkadot/types';
|
import { EventRecord, ExtrinsicEra, Hash, Header, Index, SignedBlock } from '@polkadot/types';
|
||||||
|
|
||||||
@@ -55,6 +56,28 @@ describeE2E({
|
|||||||
return api.tx(hex).send(logEvents(done));
|
return api.tx(hex).send(logEvents(done));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('invalid hex does throw a catchable exception', async (done): Promise<void> => {
|
||||||
|
const nonce = await api.query.system.accountNonce(keyring.dave.address) as Index;
|
||||||
|
const hex = api.tx.balances
|
||||||
|
.transfer(keyring.eve.address, 12345)
|
||||||
|
.sign(keyring.dave, { nonce })
|
||||||
|
.toHex();
|
||||||
|
|
||||||
|
// change to an invalid signature, 32 * 2 for hex
|
||||||
|
const sigIdx = hex.indexOf(u8aToHex(keyring.dave.publicKey).substr(2)) + 64;
|
||||||
|
const mangled = hex.replace(
|
||||||
|
hex.substr(sigIdx, 32), // first 16 bytes of sig
|
||||||
|
hex.substr(2, 32) // replaced by first 16 bytes of tx
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await api.tx(mangled).send(logEvents(done));
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('makes a transfer (sign, then send)', async (done): Promise<() => void> => {
|
it('makes a transfer (sign, then send)', async (done): Promise<() => void> => {
|
||||||
const nonce = await api.query.system.accountNonce(keyring.dave.address) as Index;
|
const nonce = await api.query.system.accountNonce(keyring.dave.address) as Index;
|
||||||
|
|
||||||
@@ -166,6 +189,31 @@ describeE2E({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('fails on a transfer with invalid time (via Signer)', async (done): Promise<void> => {
|
||||||
|
const signer: Signer = new SingleAccountSigner(keyring.alice);
|
||||||
|
|
||||||
|
api.setSigner(signer);
|
||||||
|
|
||||||
|
const nonce = await api.query.system.accountNonce(keyring.alice.address) as Index;
|
||||||
|
const signedBlock = await api.rpc.chain.getBlock() as SignedBlock;
|
||||||
|
const currentHeight = signedBlock.block.header.number;
|
||||||
|
const exERA = new ExtrinsicEra({ current: currentHeight, period: 4 });
|
||||||
|
const eraDeath = exERA.asMortalEra.death(currentHeight.toNumber());
|
||||||
|
const blockHash = signedBlock.block.header.hash;
|
||||||
|
const ex = api.tx.balances.transfer(keyring.eve.address, 12345);
|
||||||
|
|
||||||
|
await api.rpc.chain.subscribeNewHead(async (header: Header): Promise<void> => {
|
||||||
|
if (header.blockNumber.toNumber() === eraDeath - 1) {
|
||||||
|
try {
|
||||||
|
await ex.signAndSend(keyring.alice.address, { blockHash, era: exERA, nonce } as any);
|
||||||
|
} catch (error) {
|
||||||
|
expect(error.message).toMatch(/1010: Invalid Transaction \(0\)/);
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('makes a transfer with custom numeric era', async (done): Promise<void> => {
|
it('makes a transfer with custom numeric era', async (done): Promise<void> => {
|
||||||
await api.tx.balances
|
await api.tx.balances
|
||||||
.transfer(keyring.eve.address, 12345)
|
.transfer(keyring.eve.address, 12345)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { combineLatest, from, Observable, Observer, of, throwError } from 'rxjs'
|
|||||||
import { catchError, map, publishReplay, refCount, switchMap } from 'rxjs/operators';
|
import { catchError, map, publishReplay, refCount, switchMap } from 'rxjs/operators';
|
||||||
import interfaces from '@polkadot/jsonrpc';
|
import interfaces from '@polkadot/jsonrpc';
|
||||||
import { Option, StorageChangeSet, StorageData, StorageKey, Vector, createClass, createType } from '@polkadot/types';
|
import { Option, StorageChangeSet, StorageData, StorageKey, Vector, createClass, createType } from '@polkadot/types';
|
||||||
import { ExtError, assert, isFunction, isNull, logger } from '@polkadot/util';
|
import { ExtError, assert, isFunction, isNull, isNumber, logger } from '@polkadot/util';
|
||||||
|
|
||||||
const l = logger('rpc-core');
|
const l = logger('rpc-core');
|
||||||
|
|
||||||
@@ -173,7 +173,15 @@ export default class Rpc implements RpcInterface {
|
|||||||
|
|
||||||
const call = (...values: any[]): Observable<any> => {
|
const call = (...values: any[]): Observable<any> => {
|
||||||
return new Observable((observer: Observer<any>): VoidCallback => {
|
return new Observable((observer: Observer<any>): VoidCallback => {
|
||||||
let subscriptionPromise: Promise<number>;
|
let subscriptionPromise: Promise<number | void>;
|
||||||
|
|
||||||
|
const errorHandler = (error: Error): void => {
|
||||||
|
const message = this.createErrorMessage(method, error);
|
||||||
|
|
||||||
|
l.error(message);
|
||||||
|
|
||||||
|
observer.error(new ExtError(message, (error as ExtError).code, undefined));
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const params = this.formatInputs(method, values);
|
const params = this.formatInputs(method, values);
|
||||||
@@ -187,13 +195,11 @@ export default class Rpc implements RpcInterface {
|
|||||||
observer.next(this.formatOutput(method, params, result));
|
observer.next(this.formatOutput(method, params, result));
|
||||||
};
|
};
|
||||||
|
|
||||||
subscriptionPromise = this.provider.subscribe(subType, subName, paramsJson, update);
|
subscriptionPromise = this.provider
|
||||||
|
.subscribe(subType, subName, paramsJson, update)
|
||||||
|
.catch(errorHandler);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = this.createErrorMessage(method, error);
|
errorHandler(error);
|
||||||
|
|
||||||
l.error(message);
|
|
||||||
|
|
||||||
observer.error(new ExtError(message, (error as ExtError).code, undefined));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Teardown logic
|
// Teardown logic
|
||||||
@@ -209,10 +215,13 @@ export default class Rpc implements RpcInterface {
|
|||||||
// ```
|
// ```
|
||||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||||
memoized.delete(...values);
|
memoized.delete(...values);
|
||||||
|
|
||||||
// Unsubscribe from provider
|
// Unsubscribe from provider
|
||||||
subscriptionPromise
|
subscriptionPromise && subscriptionPromise
|
||||||
.then((subscriptionId): Promise<boolean> =>
|
.then((subscriptionId): Promise<boolean> =>
|
||||||
this.provider.unsubscribe(subType, unsubName, subscriptionId)
|
isNumber(subscriptionId)
|
||||||
|
? this.provider.unsubscribe(subType, unsubName, subscriptionId)
|
||||||
|
: Promise.resolve(false)
|
||||||
)
|
)
|
||||||
.catch((error: Error): void => {
|
.catch((error: Error): void => {
|
||||||
const message = this.createErrorMessage(method, error);
|
const message = this.createErrorMessage(method, error);
|
||||||
|
|||||||
Reference in New Issue
Block a user