Skip to content
Open
Show file tree
Hide file tree
Changes from 11 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: 31 additions & 0 deletions packages/api/test/e2e/promise-tx.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { randomAsHex } from '@polkadot/util-crypto';
import Api from '../../src/promise';
import WsProvider from '../../../rpc-provider/src/ws';
import SingleAccountSigner from "../util/SingleAccountSigner";
import ExtrinsicEra from '../../../types/src/type/ExtrinsicEra';
Comment thread
KarishmaBothara marked this conversation as resolved.
Outdated
import U64 from "../../../types/src/primitive/U64";

describe.skip('e2e transactions', () => {
const keyring = testingPairs({ type: 'ed25519' });
Expand Down Expand Up @@ -211,4 +213,33 @@ describe.skip('e2e transactions', () => {
doTwo(done)
});
});

it('makes a transfer with ERA (signAndSend)', async (done) => {
const nonce = await api.query.system.accountNonce(keyring.dave.address());
const signedBlock = await api.rpc.chain.getBlock();
const currentHeight = signedBlock.block.header.number;
// const exERA = new ExtrinsicEra(new Uint8Array([248, 13]), 1);
const exERA = new ExtrinsicEra({ startBlockNumber: new U64(currentHeight+10), endBlockNumber: new U64(currentHeight+50) }, 1);
const eraBirth = exERA.asMortalEra.birth(currentHeight);
const eraHash = await api.rpc.chain.getBlockHash(eraBirth);
const ex = api.tx.balances
.transfer(keyring.eve.address(), 12345);
const tx = await ex.signAndSend(keyring.dave, {blockHash: eraHash, era:exERA, nonce});
expect(tx.toHex()).toHaveLength(66);
done();
});

it('makes a transfer with ERA (signAndSend) with invalid time', async (done) => {
const nonce = await api.query.system.accountNonce(keyring.alice.address());
const signedBlock = await api.rpc.chain.getBlock();
const currentHeight = signedBlock.block.header.number;
const exERA = new ExtrinsicEra({ startBlockNumber: new U64(currentHeight-40), endBlockNumber: new U64(currentHeight+10) }, 1);
const eraBirth = exERA.asMortalEra.birth(currentHeight);
const eraHash = await api.rpc.chain.getBlockHash(eraBirth);
const ex = api.tx.balances
.transfer(keyring.eve.address(), 12345);
const tx = await ex.signAndSend(keyring.alice, {blockHash: eraHash, era:exERA, nonce});
expect(tx).toBeUndefined();
done();
});
});
30 changes: 30 additions & 0 deletions packages/types/src/type/ExtrinsicEra.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2017-2019 @polkadot/types authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.

import ExtrinsicEra, { MortalEra } from './ExtrinsicEra';
import U64 from '../primitive/U64';

describe('ExtrinsicEra', () => {

it('decodes an Extrinsic Era with immortal', () => {
const extrinsicEra = new ExtrinsicEra(new Uint8Array([0]));
expect(extrinsicEra.asMortalEra).toBeUndefined();
expect(extrinsicEra.asImmortalEra).toBeDefined();
});

it('decodes an Extrinsic Era from u8 as mortal', () => {
const extrinsicEra = new ExtrinsicEra(new Uint8Array([1, 78, 156]));
expect((extrinsicEra.asMortalEra as MortalEra).period.toNumber()).toEqual(32768);
expect((extrinsicEra.asMortalEra as MortalEra).phase.toNumber()).toEqual(20000);
});


it('encode an Extrinsic Era from Object with blocknumber & period as mortal instance', () => {
const mortalIndex = 1;
const extrinsicEra = new ExtrinsicEra({ startBlockNumber: new U64(1400), endBlockNumber: new U64(1600) }, mortalIndex);
expect((extrinsicEra.asMortalEra as MortalEra).period.toNumber()).toBeGreaterThan(4);
Comment thread
KarishmaBothara marked this conversation as resolved.
Outdated
expect((extrinsicEra.asMortalEra as MortalEra).period.toNumber()).toEqual(256);
expect((extrinsicEra.asMortalEra as MortalEra).phase.toNumber()).toEqual(120);
});
});
155 changes: 139 additions & 16 deletions packages/types/src/type/ExtrinsicEra.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,156 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.

import EnumType from '../codec/EnumType';
import {isHex, isU8a, hexToU8a, isObject} from '@polkadot/util';
import Tuple from '../codec/Tuple';
import U64 from '../primitive/U64';
import U8 from '../primitive/U8';
import U16 from '../primitive/U16';
import { AnyU8a } from '../types';
import BlockNumber from "@polkadot/types/type/BlockNumber";
import U8a from "@polkadot/types/codec/U8a";

import { u8aToU8a } from '@polkadot/util';
interface EraMethod {
startBlockNumber: BlockNumber;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

they can just be number.

endBlockNumber: BlockNumber;
}

export default class ExtrinsicEra extends EnumType<ImmortalEra | MortalEra> {
constructor (value?: any, index?: number) {
super({ ImmortalEra, MortalEra }, value, index);
}

/**
* @description Returns the item as a [[ImmortalEra]]
*/
get asImmortalEra (): ImmortalEra | undefined{
if (this.index === 0) {
return this.value as ImmortalEra;
}
return;
}

/**
* @description Returns the item as a [[MortalEra]]
*/
get asMortalEra (): MortalEra | undefined {
Comment thread
KarishmaBothara marked this conversation as resolved.
Outdated
Comment thread
KarishmaBothara marked this conversation as resolved.
Outdated
if (this.index === 1) {
Comment thread
KarishmaBothara marked this conversation as resolved.
Outdated
return this.value as MortalEra;
}
return;
}

/**
* @description Encodes the value as a Uint8Array as per the parity-codec specifications
* @param isBare true when the value has none of the type-specific prefixes (internal)
*/
toU8a (isBare?: boolean): Uint8Array {
if (this.index === 0 ) {
return super.toU8a()
} else {
return (this.asMortalEra as MortalEra).toU8a(isBare);
Comment thread
KarishmaBothara marked this conversation as resolved.
Outdated
}
}
}

export class ImmortalEra extends U8a {
constructor(value?: AnyU8a) {
super(value);
Comment thread
KarishmaBothara marked this conversation as resolved.
}
}

import U8a from '../codec/U8a';
export type MortalEraValue = [U8, U8];

/**
* @name ExtrinsicEra
* @name MortalEra
* @description
* The era for an extrinsic, indicating either a mortal or immortal extrinsic
* The MortalEra for an extrinsic, indicating period and phase
*/
export default class ExtrinsicEra extends U8a {
constructor (value?: AnyU8a) {
super(
ExtrinsicEra.decodeExtrinsicEra(value)
);
export class MortalEra extends Tuple {
Comment thread
ianhe8x marked this conversation as resolved.
constructor (value?: any) {
super({
period : U8, phase : U8
}, MortalEra.decodeMortalEra(value));
}

private static decodeMortalEra (value: EraMethod | Uint8Array | string): MortalEraValue {
Comment thread
KarishmaBothara marked this conversation as resolved.
Outdated
if (isHex(value)) {
return MortalEra.decodeMortalEra(hexToU8a(value.toString()));
} else if (isU8a(value)) {
const u8a = value;
const first = u8a.subarray(0, 1);
let second = u8a.subarray(1, 2);
const encoded = new U64(new U64(first).toNumber() + (new U64(second).toNumber() << 8));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can just use them as type number. (no point wrap them with U64 but toNumber() every time use them)

const period = new U64(2 << (encoded.toNumber() % (1 << 4)));
const factor = 12;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can just use 12 in next line

const quantizeFactor = Math.max(period.toNumber() >> factor, 1);
let phase = (encoded.toNumber() >> 4) * quantizeFactor;
if (period.toNumber() >= 4 && phase < period.toNumber()) {
return [new U8(period), new U8(phase)];
}
throw new Error('Invalid data passed to Mortal era');
} else if (isObject(value)) {
const current = value.startBlockNumber;
const period = value.endBlockNumber.toNumber() - value.startBlockNumber.toNumber();
let calPeriod = Math.pow(2, Math.ceil(Math.log2(period)));
calPeriod = Math.min( Math.max(calPeriod, 4), 1<< 16);
const phase = current.toNumber() % calPeriod;
const factor = 12;
const quantizeFactor = calPeriod >> factor > 1 ? calPeriod >> factor : 1;
let quantizedPhase = phase / quantizeFactor * quantizeFactor;
return [new U8(calPeriod), new U8(quantizedPhase)];
}
return [new U8(), new U8()];
}
/**
* @description The justification [[U64]]
*/
get period (): U8 {
return this[0] as U8;
}

/**
* @description The round this justification wraps as a [[U64]]
*/
get phase (): U8 {
return this[1] as U8;
}

/**
* @description Encodes the value as a Uint8Array as per the parity-codec specifications
* @param isBare true when the value has none of the type-specific prefixes (internal)
*/
toU8a (isBare?: boolean): Uint8Array {

const period = new U64(this.period);
Comment thread
KarishmaBothara marked this conversation as resolved.
Outdated
const phase = new U64(this.phase);
const quantize_factor = Math.max(period.toNumber() >> 12, 1);
const trailingZeros = this.getTrailingZeros(period);
const encoded = Math.min(15, Math.max(1, trailingZeros - 1)) + (((phase.toNumber() / quantize_factor) << 4));
const encode = new U16(encoded);
Comment thread
KarishmaBothara marked this conversation as resolved.
Outdated
const first = encode.toNumber() >> 8;
const second = encode.toNumber() & 0xff;
return new Uint8Array([second, first]);
}

static decodeExtrinsicEra (value?: AnyU8a): Uint8Array {
if (value) {
const u8a = u8aToU8a(value);
getTrailingZeros(period: U64) {
let zeros = '';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const zero: number[] = [];

let periodN = period.toNumber();
periodN = parseInt(Number(periodN).toString(2));
//periodN = periodN.toString(2)

// If we have a zero byte, it is immortal (1 byte in length), otherwise we have
// the era details following as another byte
return u8a.subarray(0, (u8a[0] === 0) ? 1 : 2);
while (periodN % 10 == 0) {
periodN = periodN /= 10;
zeros += 0;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

zeros.push(0)

}
return zeros.length;
}


return new Uint8Array([0]);
birth(current: U64) {
const b = Math.floor((Math.max(current.toNumber(),this.phase.toNumber()) - this.phase.toNumber()) / this.period.toNumber()) * this.period.toNumber() + this.phase.toNumber();
return new U64(b);
}

}
9 changes: 8 additions & 1 deletion packages/types/src/type/ExtrinsicSignature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ export default class ExtrinsicSignature extends Struct implements IExtrinsicSign
return (this.get('version') as U8).toNumber();
}

/**
* @description The [[ExtrinsicEra]] (mortal or immortal) this signature applies to
*/
set era (era: ExtrinsicEra) {
this.set('era', era);
}

private injectSignature (signature: Signature, signer: Address, nonce: Nonce, era: ExtrinsicEra): ExtrinsicSignature {
this.set('era', era);
this.set('nonce', nonce);
Expand Down Expand Up @@ -145,7 +152,7 @@ export default class ExtrinsicSignature extends Struct implements IExtrinsicSign
const signingPayload = new SignaturePayload({
nonce,
method,
era: era || IMMORTAL_ERA,
era: era || this.era || IMMORTAL_ERA,
blockHash
});
const signature = new Signature(signingPayload.sign(account, version as RuntimeVersion));
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export interface IMethod extends Codec {

export interface IExtrinsicSignature extends Codec {
readonly isSigned: boolean;
era: Codec;
}

export interface IExtrinsic extends IMethod {
Expand Down