Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion packages/api/src/promise/Api.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,23 @@ describe('ApiPromise', (): void => {

describe('decorator.signAsync', (): void => {
it('signs a transfer using an external signer', async (): Promise<void> => {
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);

await transfer.signAsync(aliceEd, {});

expect(transfer.signature.toHex()).toEqual(
'0x97f3cfe5088fcd575313e983f45d02b0f630e7b94ff9a3ac50e20cd096a8f554fda73d42ead891b5a1d3ce5607d83f20b0c6570b555e949cfb5763d0abcd590b'
'0x6b9ccc95afbd4e916d30c65c720f4f7b70a77db545735b48a763844aa5210e695aa346686bad1224af77d00bcfbf6fc8d2c216a60731027835d5a414186a2607'
);
});
});
Expand Down
55 changes: 28 additions & 27 deletions packages/api/src/submittable/createClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -84,15 +84,8 @@ export default function createClass <ApiType extends ApiTypes> ({ api, apiType,
// also supports signing through external signers
public signAsync (account: IKeyringPair | string | AccountId | Address, optionsOrNonce: Partial<SignerOptions>): SubmittableThis<ApiType, this> {
return this._decorateMethod(
(): Observable<this> => {
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> =>
this._signObservable(account, optionsOrNonce).pipe(mapTo(this))
)();
}

Expand All @@ -109,27 +102,15 @@ export default function createClass <ApiType extends ApiTypes> ({ api, apiType,
public signAndSend (account: IKeyringPair | string | AccountId | Address, optionsOrStatus?: Partial<SignerOptions> | Callback<SubmittableResultImpl>, optionalStatusCb?: Callback<SubmittableResultImpl>): SubmittableResultResult<ApiType> | SubmittableResultSubscription<ApiType> {
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<Codec> => (
this._getPrelimState(address, options).pipe(
first(),
mergeMap(async ([nonce, header]): Promise<void> => {
const eraOptions = this._makeEraOptions(options, { header, nonce });

if (isKeyringPair(account)) {
this.sign(account, eraOptions);
} else {
updateId = await this._signViaSigner(address, eraOptions, header);
}
}),
switchMap((): Observable<SubmittableResultImpl> | Observable<Hash> => {
return isSubscription
this._signObservable(account, options).pipe(
switchMap((updateId: number | undefined): Observable<SubmittableResultImpl> | Observable<Hash> =>
isSubscription
? this._subscribeObservable(updateId)
: this._sendObservable(updateId);
})
: this._sendObservable(updateId)
)
) as Observable<Codec>) // FIXME This is wrong, SubmittableResult is _not_ a codec
)(statusCb);
}
Expand Down Expand Up @@ -163,6 +144,26 @@ export default function createClass <ApiType extends ApiTypes> ({ api, apiType,
return [options, statusCb];
}

private _signObservable (account: IKeyringPair | string | AccountId | Address, optionsOrNonce: Partial<SignerOptions>): Observable<number | undefined> {
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<void> => {
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<number> {
const signer = options.signer || this._api.signer;

Expand Down
29 changes: 16 additions & 13 deletions packages/rpc-provider/src/mock/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -51,19 +52,18 @@ export default class Mock implements ProviderInterface {

private registry: Registry;

private prevNumber = new BN(-1);

private requests: Record<string, (...params: any[]) => 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<string, number | string> => ({ ss58Format: 42 }),
system_version: (): string => '9.8.7'
Expand Down Expand Up @@ -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);
Expand All @@ -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 => {
Expand All @@ -192,20 +192,23 @@ 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: []
},
extrinsicsRoot: randomAsU8a(),
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 {
Expand Down
16 changes: 16 additions & 0 deletions packages/types/src/json/Header.004.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"jsonrpc": "2.0",
"result": {
"digest": {
"logs": [
"0x0642414245b5010100000000d968651f00000000c8a188930c6c1a8fb15316992a36f29671049dfdb96d9248747e90b80c48547af89b3238620eaec599e6a5bfedf3ae538a6cb4e19326ac2af9c21494713fc70af38629db7bcc42bf8110655d7b3bb1cec3790e39c660111e7fac4a0cba058305",
"0x05424142450101168781581fefb96e500cfd56dbf5b9709a6babb19f74369ea7b660ea861e180c08899182e4beb8da95848b87a16a64132f497cea3cb7aa6e3f3224adb9945989"
]
},
"extrinsicsRoot": "0x82faad984718975bccbfd58fdc67f59d367d678419a918f79c7a58cbb8e34a5b",
"number": "0x14fd",
"parentHash": "0xf0b30ecdc94c481ca96234d2a17408fef6c401f705a1f5f911e58d2a1f33fcb6",
"stateRoot": "0xaa796217f92ed5a40ec03d184b05ad1e1824d2824559719b9bef3304ee3bff55"
},
"id": 1
}