diff --git a/packages/api/src/promise/Api.spec.ts b/packages/api/src/promise/Api.spec.ts index d61b47f85c..47d430a30c 100644 --- a/packages/api/src/promise/Api.spec.ts +++ b/packages/api/src/promise/Api.spec.ts @@ -90,6 +90,15 @@ describe('ApiPromise', (): void => { describe('decorator.signAsync', (): void => { it('signs a transfer using an external signer', async (): Promise => { + provider.subscriptions.state_subscribeStorage.lastValue = { + changes: [ + [ + '0x26aa394eea5630e07c48ae0c9558cef79c2f82b23e5fd031fb54c292794b4cc4d560eb8d00e57357cf76492334e43bb2ecaa9f28df6a8c4426d7b6090f7ad3c9', + '0x00' + ] + ] + }; + const signer = new SingleAccountSigner(registry, aliceEd); const api = await ApiPromise.create({ provider, registry, signer }); const transfer = api.tx.balances.transfer(keyring.getPair('0xe659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e').address, 12345); @@ -97,7 +106,7 @@ describe('ApiPromise', (): void => { await transfer.signAsync(aliceEd, {}); expect(transfer.signature.toHex()).toEqual( - '0x97f3cfe5088fcd575313e983f45d02b0f630e7b94ff9a3ac50e20cd096a8f554fda73d42ead891b5a1d3ce5607d83f20b0c6570b555e949cfb5763d0abcd590b' + '0x6b9ccc95afbd4e916d30c65c720f4f7b70a77db545735b48a763844aa5210e695aa346686bad1224af77d00bcfbf6fc8d2c216a60731027835d5a414186a2607' ); }); }); diff --git a/packages/api/src/submittable/createClass.ts b/packages/api/src/submittable/createClass.ts index eabeb283cc..2420227e55 100644 --- a/packages/api/src/submittable/createClass.ts +++ b/packages/api/src/submittable/createClass.ts @@ -8,7 +8,7 @@ import { Callback, Codec, Constructor, IKeyringPair, Registry, SignatureOptions import { ApiInterfaceRx, ApiTypes, SignerResult } from '../types'; import { SignerOptions, SubmittableExtrinsic, SubmittablePaymentResult, SubmittableResultImpl, SubmittableResultResult, SubmittableResultSubscription, SubmittableThis } from './types'; -import { Observable, combineLatest, from, of } from 'rxjs'; +import { Observable, combineLatest, of } from 'rxjs'; import { first, map, mapTo, mergeMap, switchMap, tap } from 'rxjs/operators'; import { createType, ClassOf } from '@polkadot/types'; import { assert, isBn, isFunction, isNumber, isUndefined } from '@polkadot/util'; @@ -84,15 +84,8 @@ export default function createClass ({ api, apiType, // also supports signing through external signers public signAsync (account: IKeyringPair | string | AccountId | Address, optionsOrNonce: Partial): SubmittableThis { return this._decorateMethod( - (): Observable => { - const optionsWithNonce = this._optionsOrNonce(optionsOrNonce); - - return from( - isKeyringPair(account) - ? Promise.resolve(this.sign(account, optionsWithNonce)) - : this._signViaSigner(account, this._makeSignOptions(optionsWithNonce, {}), null) - ).pipe(mapTo(this)); - } + (): Observable => + this._signObservable(account, optionsOrNonce).pipe(mapTo(this)) )(); } @@ -109,27 +102,15 @@ export default function createClass ({ api, apiType, public signAndSend (account: IKeyringPair | string | AccountId | Address, optionsOrStatus?: Partial | Callback, optionalStatusCb?: Callback): SubmittableResultResult | SubmittableResultSubscription { const [options, statusCb] = this._makeSignAndSendOptions(optionsOrStatus, optionalStatusCb); const isSubscription = this._api.hasSubscriptions && (this._ignoreStatusCb || !!statusCb); - const address = isKeyringPair(account) ? account.address : account.toString(); - let updateId: number | undefined; return this._decorateMethod( (): Observable => ( - this._getPrelimState(address, options).pipe( - first(), - mergeMap(async ([nonce, header]): Promise => { - const eraOptions = this._makeEraOptions(options, { header, nonce }); - - if (isKeyringPair(account)) { - this.sign(account, eraOptions); - } else { - updateId = await this._signViaSigner(address, eraOptions, header); - } - }), - switchMap((): Observable | Observable => { - return isSubscription + this._signObservable(account, options).pipe( + switchMap((updateId: number | undefined): Observable | Observable => + isSubscription ? this._subscribeObservable(updateId) - : this._sendObservable(updateId); - }) + : this._sendObservable(updateId) + ) ) as Observable) // FIXME This is wrong, SubmittableResult is _not_ a codec )(statusCb); } @@ -163,6 +144,26 @@ export default function createClass ({ api, apiType, return [options, statusCb]; } + private _signObservable (account: IKeyringPair | string | AccountId | Address, optionsOrNonce: Partial): Observable { + const address = isKeyringPair(account) ? account.address : account.toString(); + const options = this._optionsOrNonce(optionsOrNonce); + let updateId: number | undefined; + + return this._getPrelimState(address, options).pipe( + first(), + mergeMap(async ([nonce, header]): Promise => { + const eraOptions = this._makeEraOptions(options, { header, nonce }); + + if (isKeyringPair(account)) { + this.sign(account, eraOptions); + } else { + updateId = await this._signViaSigner(address, eraOptions, header); + } + }), + mapTo(updateId) + ); + } + private async _signViaSigner (address: | string | AccountId | Address, options: SignatureOptions, header: Header | null): Promise { const signer = options.signer || this._api.signer; diff --git a/packages/rpc-provider/src/mock/index.ts b/packages/rpc-provider/src/mock/index.ts index b61182a959..080c6749f4 100644 --- a/packages/rpc-provider/src/mock/index.ts +++ b/packages/rpc-provider/src/mock/index.ts @@ -14,6 +14,7 @@ import Metadata from '@polkadot/metadata/Decorated'; import rpcMetadata from '@polkadot/metadata/Metadata/static'; import interfaces from '@polkadot/jsonrpc'; import testKeyring from '@polkadot/keyring/testing'; +import rpcHeader from '@polkadot/types/json/Header.004.json'; import rpcSignedBlock from '@polkadot/types/json/SignedBlock.004.immortal.json'; import { createType } from '@polkadot/types'; import { bnToU8a, logger, u8aToHex } from '@polkadot/util'; @@ -51,19 +52,18 @@ export default class Mock implements ProviderInterface { private registry: Registry; + private prevNumber = new BN(-1); + private requests: Record any> = { // eslint-disable-next-line @typescript-eslint/no-unused-vars chain_getBlock: (hash: string): any => createType(this.registry, 'SignedBlock', rpcSignedBlock.result).toJSON(), // eslint-disable-next-line @typescript-eslint/no-unused-vars chain_getBlockHash: (blockNumber: number): string => '0x1234', + chain_getHeader: (): any => createType(this.registry, 'Header', rpcHeader.result).toJSON(), state_getRuntimeVersion: (): string => createType(this.registry, 'RuntimeVersion').toHex(), - state_getStorage: (storage: MockStateDb, params: any[]): string => { - return u8aToHex( - storage[(params[0] as string)] - ); - }, - system_chain: (): string => 'mockChain', state_getMetadata: (): string => rpcMetadata, + state_getStorage: (storage: MockStateDb, params: any[]): string => u8aToHex(storage[(params[0] as string)]), + system_chain: (): string => 'mockChain', system_name: (): string => 'mockClient', system_properties: (): Record => ({ ss58Format: 42 }), system_version: (): string => '9.8.7' @@ -157,7 +157,7 @@ export default class Mock implements ProviderInterface { private init (): void { const emitEvents: ProviderInterfaceEmitted[] = ['connected', 'disconnected']; let emitIndex = 0; - let newHead = this.makeBlockHeader(new BN(-1)); + let newHead = this.makeBlockHeader(); let counter = -1; const metadata = new Metadata(this.registry, rpcMetadata); @@ -169,7 +169,7 @@ export default class Mock implements ProviderInterface { } // create a new header (next block) - newHead = this.makeBlockHeader(newHead.number.toBn()); + newHead = this.makeBlockHeader(); // increment the balances and nonce for each account keyring.getPairs().forEach(({ publicKey }, index): void => { @@ -192,10 +192,9 @@ export default class Mock implements ProviderInterface { }, INTERVAL); } - private makeBlockHeader (prevNumber: BN): Header { - const blockNumber = prevNumber.addn(1); - - return createType(this.registry, 'Header', { + private makeBlockHeader (): Header { + const blockNumber = this.prevNumber.addn(1); + const header = createType(this.registry, 'Header', { digest: { logs: [] }, @@ -203,9 +202,13 @@ export default class Mock implements ProviderInterface { number: blockNumber, parentHash: blockNumber.isZero() ? new Uint8Array(32) - : bnToU8a(prevNumber, 256, false), + : bnToU8a(this.prevNumber, 256, false), stateRoot: bnToU8a(blockNumber, 256, false) }); + + this.prevNumber = blockNumber; + + return header; } private setStateBn (key: Uint8Array, value: BN | number): void { diff --git a/packages/types/src/json/Header.004.json b/packages/types/src/json/Header.004.json new file mode 100644 index 0000000000..a5c4761bfa --- /dev/null +++ b/packages/types/src/json/Header.004.json @@ -0,0 +1,16 @@ +{ + "jsonrpc": "2.0", + "result": { + "digest": { + "logs": [ + "0x0642414245b5010100000000d968651f00000000c8a188930c6c1a8fb15316992a36f29671049dfdb96d9248747e90b80c48547af89b3238620eaec599e6a5bfedf3ae538a6cb4e19326ac2af9c21494713fc70af38629db7bcc42bf8110655d7b3bb1cec3790e39c660111e7fac4a0cba058305", + "0x05424142450101168781581fefb96e500cfd56dbf5b9709a6babb19f74369ea7b660ea861e180c08899182e4beb8da95848b87a16a64132f497cea3cb7aa6e3f3224adb9945989" + ] + }, + "extrinsicsRoot": "0x82faad984718975bccbfd58fdc67f59d367d678419a918f79c7a58cbb8e34a5b", + "number": "0x14fd", + "parentHash": "0xf0b30ecdc94c481ca96234d2a17408fef6c401f705a1f5f911e58d2a1f33fcb6", + "stateRoot": "0xaa796217f92ed5a40ec03d184b05ad1e1824d2824559719b9bef3304ee3bff55" + }, + "id": 1 +}