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:
Jaco Greeff
2019-07-16 20:01:31 +02:00
committed by GitHub
parent 970f689309
commit eaec0da2f5
3 changed files with 68 additions and 11 deletions
+1 -1
View File
@@ -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)
+19 -10
View File
@@ -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);