Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adjust ExtrinsicV5 to fit current spec #6029

Merged
merged 9 commits into from
Nov 18, 2024
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
31 changes: 7 additions & 24 deletions packages/types/src/extrinsic/Extrinsic.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,36 +57,19 @@ describe('Extrinsic', (): void => {
registry.setMetadata(metadata);

describe('SignedExtrinsic', () => {
it('Should work when the version and preamble is passed in', () => {
const extrinsic = new Extrinsic(
it('Should error with a signed extrinsic, when the version is passed in', () => {
expect(() => new Extrinsic(
registry,
'0x51028500d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d011e0b7d9438899333c50121f8e10144952d51c3bb8d0ea11dd1f24940d8ff615ad351d95ed9f41f078748ed7cf182864a20b38eebfaef6629433365eb90c0148c007502000000000603008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480b00a0724e1809',
{ preamble: 'signed', version: 5 }
);

expect(extrinsic.signer.toString()).toEqual('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY');
expect(extrinsic.era.toHuman()).toEqual({ MortalEra: { period: '64', phase: '39' } });
expect(extrinsic.nonce.toNumber()).toEqual(0);
expect(extrinsic.tip.toHuman()).toEqual('0');
expect(extrinsic.callIndex).toEqual(new Uint8Array([6, 3]));
expect(extrinsic.args[0].toHex()).toEqual('0x008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48');
expect(extrinsic.args[1].toHuman()).toEqual('10,000,000,000,000');
{ version: 5 }
)).toThrow('Signed Extrinsics are currently only available for ExtrinsicV4');
});

it('Should work when the version and preamble is not passed in', () => {
const extrinsic = new Extrinsic(
it('Should error when the version and preamble is not passed in, and its a signed extrinsic', () => {
expect(() => new Extrinsic(
registry,
'0x51028500d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d011e0b7d9438899333c50121f8e10144952d51c3bb8d0ea11dd1f24940d8ff615ad351d95ed9f41f078748ed7cf182864a20b38eebfaef6629433365eb90c0148c007502000000000603008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480b00a0724e1809'
);

expect(extrinsic.version).toEqual(133);
expect(extrinsic.signer.toString()).toEqual('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY');
expect(extrinsic.era.toHuman()).toEqual({ MortalEra: { period: '64', phase: '39' } });
expect(extrinsic.nonce.toNumber()).toEqual(0);
expect(extrinsic.tip.toHuman()).toEqual('0');
expect(extrinsic.callIndex).toEqual(new Uint8Array([6, 3]));
expect(extrinsic.args[0].toHex()).toEqual('0x008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48');
expect(extrinsic.args[1].toHuman()).toEqual('10,000,000,000,000');
)).toThrow('Signed Extrinsics are currently only available for ExtrinsicV4');
});
});

Expand Down
18 changes: 9 additions & 9 deletions packages/types/src/extrinsic/Extrinsic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import type { ExtrinsicValueV5 } from './v5/Extrinsic.js';
import { AbstractBase } from '@polkadot/types-codec';
import { compactAddLength, compactFromU8a, compactToU8a, isHex, isU8a, objectProperty, objectSpread, u8aConcat, u8aToHex, u8aToU8a } from '@polkadot/util';

import { BARE_EXTRINSIC, BIT_SIGNED, BIT_UNSIGNED, DEFAULT_PREAMBLE, GENERAL_EXTRINSIC, LATEST_EXTRINSIC_VERSION, LOWEST_SUPPORTED_EXTRINSIC_FORMAT_VERSION, SIGNED_EXTRINSIC, TYPE_MASK, VERSION_MASK } from './constants.js';
import { BARE_EXTRINSIC, BIT_SIGNED, BIT_UNSIGNED, DEFAULT_PREAMBLE, GENERAL_EXTRINSIC, LATEST_EXTRINSIC_VERSION, LOWEST_SUPPORTED_EXTRINSIC_FORMAT_VERSION, TYPE_MASK, VERSION_MASK } from './constants.js';

interface CreateOptions {
version?: number;
Expand All @@ -40,22 +40,18 @@ const VERSIONS = [

const PREAMBLE = {
bare: 'ExtrinsicV5',
general: 'GeneralExtrinsic',
signed: 'ExtrinsicV5'
general: 'GeneralExtrinsic'
};

const PreambleMask = {
bare: BARE_EXTRINSIC,
general: GENERAL_EXTRINSIC,
signed: SIGNED_EXTRINSIC
general: GENERAL_EXTRINSIC
};

const preambleUnMask: Record<string, Preamble> = {
0: 'bare',
// eslint-disable-next-line sort-keys
64: 'general',
// eslint-disable-next-line sort-keys
128: 'signed'
64: 'general'
};

export { LATEST_EXTRINSIC_VERSION };
Expand Down Expand Up @@ -295,7 +291,11 @@ abstract class ExtrinsicBase<A extends AnyTuple> extends AbstractBase<ExtrinsicV
if (this.type <= LOWEST_SUPPORTED_EXTRINSIC_FORMAT_VERSION) {
return this.type | (this.isSigned ? BIT_SIGNED : BIT_UNSIGNED);
} else {
return this.type | (this.isSigned ? PreambleMask.signed : this.isGeneral() ? PreambleMask.general : PreambleMask.bare);
if (this.isSigned) {
throw new Error('Signed Extrinsics are currently only available for ExtrinsicV4');
}

return this.type | (this.isGeneral() ? PreambleMask.general : PreambleMask.bare);
}
}

Expand Down
3 changes: 1 addition & 2 deletions packages/types/src/extrinsic/ExtrinsicPayload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ const VERSIONS = [
const PREAMBLES = {
bare: 'ExtrinsicPayloadV5',
// Not supported yet
general: 'ExtrinsicPayloadV5',
signed: 'ExtrinsicPayloadV5'
general: 'ExtrinsicPayloadV5'
};

/** @internal */
Expand Down
2 changes: 0 additions & 2 deletions packages/types/src/extrinsic/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ export const TYPE_MASK = 0b11000000;

export const BARE_EXTRINSIC = 0b00000000;

export const SIGNED_EXTRINSIC = 0b10000000;

export const GENERAL_EXTRINSIC = 0b01000000;

export const LOWEST_SUPPORTED_EXTRINSIC_FORMAT_VERSION = 4;
2 changes: 1 addition & 1 deletion packages/types/src/extrinsic/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ export interface ExtrinsicExtraValue {
tip?: AnyNumber;
}

export type Preamble = 'signed' | 'bare' | 'general';
export type Preamble = 'bare' | 'general';
7 changes: 0 additions & 7 deletions packages/types/src/extrinsic/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,6 @@ export function sign (registry: Registry, signerPair: IKeyringPair, u8a: Uint8Ar
return signerPair.sign(encoded, options);
}

// a helper function for both types of payloads, Raw and metadata-known
export function signV5 (registry: Registry, signerPair: IKeyringPair, u8a: Uint8Array, options?: SignOptions): Uint8Array {
const encoded = registry.hash(u8a);

return signerPair.sign(encoded, options);
}

export function signGeneral (registry: Registry, u8a: Uint8Array): Uint8Array {
const encoded = registry.hash(u8a);

Expand Down
19 changes: 3 additions & 16 deletions packages/types/src/extrinsic/v5/Extrinsic.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ describe('ExtrinsicV5', (): void => {
);
});

it('creates a signed extrinsic', (): void => {
it('Errors when creating a signed Extrinsic', (): void => {
expect(
new Extrinsic(
() => new Extrinsic(
registry,
tx['balances']['transferAllowDeath'](keyring.bob.publicKey, 6969n)
).sign(keyring.alice, {
Expand All @@ -69,19 +69,6 @@ describe('ExtrinsicV5', (): void => {
},
tip: 2
}).toHex()
).toEqual(
'0x' +
'00' +
'd172a74cda4c865912c32ba0a80a57ae69abae410e5ccb59dee84e2f4432db4f' +
'00' + // ed25519
'84181ebef350cc212e70e042b6ebcd33ca955bf9497711a64aa7c64e2b8c2732' +
'ab837715364eab7be5cc76f74eefa36d3ba9ee698264ed28a286c8360fc9aa0c' +
'00' + // TransactionExtension version
'0004080000' + // era. nonce, tip, mode
'0600' +
'00' +
'd7568e5f0a7eda67a82691ff379ac4bba4f9c9b859fe779b5d46363b61ad2db9' +
'e56c'
);
).toThrow('Extrinsic: ExtrinsicV5 does not include signing support');
});
});
24 changes: 12 additions & 12 deletions packages/types/src/extrinsic/v5/Extrinsic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,28 +89,28 @@ export class GenericExtrinsicV5 extends Struct implements IExtrinsicV5Impl {

/**
* @description Add an [[ExtrinsicSignatureV5]] to the extrinsic (already generated)
*
* [Disabled for ExtrinsicV5]
*/
public addSignature (signer: Address | Uint8Array | string, signature: Uint8Array | HexString, payload: ExtrinsicPayloadValue | Uint8Array | HexString): GenericExtrinsicV5 {
this.signature.addSignature(signer, signature, payload);

return this;
public addSignature (_signer: Address | Uint8Array | string, _signature: Uint8Array | HexString, _payload: ExtrinsicPayloadValue | Uint8Array | HexString): GenericExtrinsicV5 {
throw new Error('Extrinsic: ExtrinsicV5 does not include signing support');
}

/**
* @description Sign the extrinsic with a specific keypair
*
* [Disabled for ExtrinsicV5]
*/
public sign (account: IKeyringPair, options: SignatureOptions): GenericExtrinsicV5 {
this.signature.sign(this.method, account, options);

return this;
public sign (_account: IKeyringPair, _options: SignatureOptions): GenericExtrinsicV5 {
throw new Error('Extrinsic: ExtrinsicV5 does not include signing support');
}

/**
* @describe Adds a fake signature to the extrinsic
*
* [Disabled for ExtrinsicV5]
*/
public signFake (signer: Address | Uint8Array | string, options: SignatureOptions): GenericExtrinsicV5 {
this.signature.signFake(this.method, signer, options);

return this;
public signFake (_signer: Address | Uint8Array | string, _options: SignatureOptions): GenericExtrinsicV5 {
throw new Error('Extrinsic: ExtrinsicV5 does not include signing support');
}
}
25 changes: 5 additions & 20 deletions packages/types/src/extrinsic/v5/ExtrinsicPayload.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright 2017-2024 @polkadot/types authors & contributors
// SPDX-License-Identifier: Apache-2.0

import type { SignOptions } from '@polkadot/keyring/types';
import type { Hash, MultiLocation } from '@polkadot/types/interfaces';
import type { Bytes } from '@polkadot/types-codec';
import type { Inspect, Registry } from '@polkadot/types-codec/types';
Expand All @@ -10,33 +9,22 @@ import type { BlockHash } from '../../interfaces/chain/index.js';
import type { ExtrinsicEra } from '../../interfaces/extrinsics/index.js';
import type { ExtrinsicPayloadValue, ICompact, IKeyringPair, INumber, IOption } from '../../types/index.js';

import { Enum, Struct } from '@polkadot/types-codec';
import { Struct } from '@polkadot/types-codec';
import { objectSpread } from '@polkadot/util';

import { signV5 } from '../util.js';

/**
* @name GenericExtrinsicPayloadV5
* @description
* A signing payload for an [[Extrinsic]]. For the final encoding, it is
* variable length based on the contents included
*/
export class GenericExtrinsicPayloadV5 extends Struct {
#signOptions: SignOptions;

constructor (registry: Registry, value?: ExtrinsicPayloadValue | Uint8Array | HexString) {
super(registry, objectSpread(
{ method: 'Bytes' },
registry.getSignedExtensionTypes(),
registry.getSignedExtensionExtra()
), value);

// Do detection for the type of extrinsic, in the case of MultiSignature
// this is an enum, in the case of AnySignature, this is a Hash only
// (which may be 64 or 65 bytes)
this.#signOptions = {
withType: registry.createTypeUnsafe('ExtrinsicSignature', []) instanceof Enum
};
}

/**
Expand Down Expand Up @@ -118,13 +106,10 @@ export class GenericExtrinsicPayloadV5 extends Struct {

/**
* @description Sign the payload with the keypair
*
* [Disabled for ExtrinsicV5]
*/
public sign (signerPair: IKeyringPair): Uint8Array {
// NOTE The `toU8a({ method: true })` argument is absolutely critical, we
// don't want the method (Bytes) to have the length prefix included. This
// means that the data-as-signed is un-decodable, but is also doesn't need
// the extra information, only the pure data (and is not decoded) ...
// The same applies to V1..V3, if we have a V6, carry this comment
return signV5(this.registry, signerPair, this.toU8a({ method: true }), this.#signOptions);
public sign (_signerPair: IKeyringPair): Uint8Array {
throw new Error('Extrinsic: ExtrinsicV5 does not include signing support');
}
}
76 changes: 8 additions & 68 deletions packages/types/src/extrinsic/v5/ExtrinsicSignature.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,25 +61,15 @@ describe('ExtrinsicSignatureV4', (): void => {
registry.setMetadata(metadata);

expect(
new ExtrinsicSignature(registry).signFake(
() => new ExtrinsicSignature(registry).signFake(
registry.createType('Call'),
pairs.alice.publicKey,
signOptions
).toHex()
).toEqual(
'0x' +
'00' + // MultiAddress
'd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d' +
'01' +
'0101010101010101010101010101010101010101010101010101010101010101' +
'0101010101010101010101010101010101010101010101010101010101010101' +
'00' + // TransactionExtension version
'00a5010000' +
'00' // Mode
);
).toThrow('Extrinsic: ExtrinsicV5 does not include signing support');
});

it('fake signs default (AccountId address)', (): void => {
it('Errors on fake sign', (): void => {
const registry = new TypeRegistry();
const metadata = new Metadata(registry, metadataStatic);

Expand All @@ -90,76 +80,26 @@ describe('ExtrinsicSignatureV4', (): void => {
});

expect(
new ExtrinsicSignature(registry).signFake(
() => new ExtrinsicSignature(registry).signFake(
registry.createType('Call'),
pairs.alice.address,
signOptions
).toHex()
).toEqual(
'0x' +
// Address = AccountId, no prefix
'd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d' +
// This is a prefix-less signature, anySignture as opposed to Multi above
'0101010101010101010101010101010101010101010101010101010101010101' +
'0101010101010101010101010101010101010101010101010101010101010101' +
'00' + // TransactionExtension version
'00a5010000' +
'00' // mode
);
).toThrow('Extrinsic: ExtrinsicV5 does not include signing support');
});

it('fake signs with non-enum signature', (): void => {
const registry = new TypeRegistry();
const metadata = new Metadata(registry, metadataStatic);

registry.setMetadata(metadata);
registry.register({
Address: 'AccountId',
ExtrinsicSignature: '[u8;65]'
});

expect(
new ExtrinsicSignature(registry).signFake(
registry.createType('Call'),
pairs.alice.address,
signOptions
).toHex()
).toEqual(
'0x' +
// Address = AccountId, no prefix
'd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d' +
// 65 bytes here
'01' +
'0101010101010101010101010101010101010101010101010101010101010101' +
'0101010101010101010101010101010101010101010101010101010101010101' +
'00' + // TransactionExtension version
'00a5010000' +
'00' // mode
);
});

it('injects a signature', (): void => {
it('Errors on injecting a signature', (): void => {
const registry = new TypeRegistry();
const metadata = new Metadata(registry, metadataStatic);

registry.setMetadata(metadata);

expect(
new ExtrinsicSignature(registry).addSignature(
() => new ExtrinsicSignature(registry).addSignature(
pairs.alice.publicKey,
new Uint8Array(65).fill(1),
new Uint8Array(0)
).toHex()
).toEqual(
'0x' +
'00' + // MultiAddress
'd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d' +
'01' +
'0101010101010101010101010101010101010101010101010101010101010101' +
'0101010101010101010101010101010101010101010101010101010101010101' +
'00' + // TransactionExtension version
'00000000' +
'00' // mode
);
).toThrow('Extrinsic: ExtrinsicV5 does not include signing support');
});
});
Loading
Loading