Skip to content

Commit

Permalink
Add resume program extrinsics (#1301)
Browse files Browse the repository at this point in the history
  • Loading branch information
osipov-mit authored Jun 23, 2023
1 parent b6af156 commit 04e4780
Show file tree
Hide file tree
Showing 21 changed files with 402 additions and 65 deletions.
4 changes: 3 additions & 1 deletion api/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
## master

_06/19/2023_
_06/20/2023_

https://github.com/gear-tech/gear-js/pull/1298
https://github.com/gear-tech/gear-js/pull/1301

### Changes

- New approach to generate program IDs
- Support `resumeSessionInit`, `resumeSessionPush` and `resumeSessionCommit` extrinsics according to https://github.com/gear-tech/gear/pull/2622

## 0.31.2

Expand Down
34 changes: 34 additions & 0 deletions api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,40 @@ console.log(gas.toHuman());
```
---

### Resume paused program

To resume paused program use `api.program.resumeSession` methods.
`init` - To start new session to resume program
`push` - To push a bunch of the program pages
`commit` - To finish resume session

```javascript
const program = await api.programStorage.getProgram(programId, oneBlockBeforePauseHash);
const initTx = api.program.resumeSession.init({
programId,
allocations: program.allocations,
codeHash: program.codeHash.toHex(),
});

let sessionId: HexString;
initTx.signAndSend(account, ({ events }) => {
events.forEach(({ event: { method, data }}) => {
if (method === 'ProgramResumeSessionStarted') {
sessionId = data.sessionId.toNumber();
}
})
})

const pages = await api.programStorage.getProgramPages(programId, program, oneBlockBeforePauseHash);
for (const memPage of Object.entries(page)) {
const tx = api.program.resumeSession.push({ sessionId, memoryPages: [memPage] });
tx.signAndSend(account);
}

const tx = api.program.resumeSession.commit({ sessionId, blockCount: 20_000 });
tx.signAndSend(account);
```

## Work with programs and blockchain state

### Check that the address belongs to some program
Expand Down
48 changes: 24 additions & 24 deletions api/programs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion api/src/Message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class GearMessage extends GearTransaction {
* @param args Message parameters
* @param meta Program metadata obtained using `getProgramMetadata` function.
* @param typeIndex (optional) Index of type in the registry. If not specified the type index from `meta.handle.input` will be used instead.
* @returns Submitted result
* @returns Submittable result
* ```javascript
* const programId = '0x..';
* const hexMeta = '0x...';
Expand Down
3 changes: 3 additions & 0 deletions api/src/Program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,18 @@ import {
} from './utils';
import { GearApi } from './GearApi';
import { GearGas } from './Gas';
import { GearResumeSession } from './ResumeSession';
import { GearTransaction } from './Transaction';
import { ProgramMetadata } from './metadata';

export class GearProgram extends GearTransaction {
public calculateGas: GearGas;
public resumeSession: GearResumeSession;

constructor(protected _api: GearApi) {
super(_api);
this.calculateGas = new GearGas(_api);
this.resumeSession = new GearResumeSession(_api);
}

/**
Expand Down
120 changes: 120 additions & 0 deletions api/src/ResumeSession.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { hexToU8a, u8aToHex } from '@polkadot/util';
import { ISubmittableResult } from '@polkadot/types/types';
import { SubmittableExtrinsic } from '@polkadot/api/types';

import { IResumeSessionCommitArgs, IResumeSessionInitArgs, IResumeSessionPushArgs } from './types';
import { ResumeSessionCommitError, ResumeSessionInitError, ResumeSessionPushError } from './errors';
import { CreateType } from './metadata';
import { GearApi } from './GearApi';
import { GearTransaction } from './Transaction';

const SIXTEEN_KB = 16384;

export class GearResumeSession extends GearTransaction {
constructor(protected _api: GearApi) {
super(_api);
}

/**
* ## Create a session for program resume. Get session id from `ProgramResumeSessionStarted` event
* @param args Resume program args
* @returns Submittable result
* @example
* ```javascript
* const program = await api.programStorage.getProgram(programId, oneBlockBeforePauseHash);
* const initTx = api.program.resumeSession.init({
* programId,
* allocations: program.allocations,
* codeHash: program.codeHash.toHex(),
* });
*
* let sessionId: HexString;
* initTx.signAndSend(account, ({ events }) => {
* events.forEach(({ event: { method, data }}) => {
* if (method === 'ProgramResumeSessionStarted') {
* sessionId = data.sessionId.toNumber();
* }
* })
* })
* ```
*/
init({
programId,
allocations,
codeHash,
}: IResumeSessionInitArgs): SubmittableExtrinsic<'promise', ISubmittableResult> {
try {
this.extrinsic = this._api.tx.gear.resumeSessionInit(
programId,
Array.from(CreateType.create('BTreeSet<u32>', allocations).toU8a()),
codeHash,
);
return this.extrinsic;
} catch (error) {
console.log(error);
throw new ResumeSessionInitError(programId, error.message);
}
}

/**
* ## Append program memory pages to the session data.
* @param args Push pages args
* @returns Submittable result
* @example
* ```javascript
* const pages = await api.programStorage.getProgramPages(programId, program, oneBlockBeforePauseHash);
* for (const memPage of Object.entries(page)) {
* const tx = api.program.resumeSession.push({ sessionId, memoryPages: [memPage] });
* tx.signAndSend(account);
* }
* ```
*/
push({ sessionId, memoryPages }: IResumeSessionPushArgs): SubmittableExtrinsic<'promise', ISubmittableResult> {
if (
!memoryPages.every(([_, page]) => {
if (typeof page === 'string') {
return page.length === SIXTEEN_KB * 2 + 2;
} else {
return page.length === SIXTEEN_KB;
}
})
) {
throw new ResumeSessionPushError(sessionId, 'Invalid memory page length. Must be 16KB.');
}

const vecLen = CreateType.create('Compact<u8>', memoryPages.length).toHex();

const tuples = memoryPages.map(([number, page]) => {
const num = CreateType.create('u32', number).toHex();
const p = typeof page === 'string' ? page : u8aToHex(page);
return num + p.slice(2);
});

try {
this.extrinsic = this._api.tx.gear.resumeSessionPush(sessionId, vecLen + tuples.slice(2));
return this.extrinsic;
} catch (error) {
throw new ResumeSessionPushError(sessionId);
}
}

/**
* ## Finish program resume session with the given key `sessionId`.
* @param args Commit session args
* @returns Submittable result
* @example
* ```javascript
* const tx = api.program.resumeSession.commit({ sessionId, blockCount: 20_000 });
* tx.signAndSend(account);
* ```
*/
commit({ sessionId, blockCount }: IResumeSessionCommitArgs): SubmittableExtrinsic<'promise', ISubmittableResult> {
try {
this.extrinsic = this._api.tx.gear.resumeSessionCommit(sessionId, blockCount);
return this.extrinsic;
} catch (error) {
console.log(error);
throw new ResumeSessionCommitError(sessionId, error.message);
}
}
}
6 changes: 4 additions & 2 deletions api/src/Storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ export class GearProgramStorage {
* @returns
*/
async getProgram(id: HexString, at?: HexString): Promise<ActiveProgram> {
const programOption = (await this._api.query.gearProgram.programStorage(id, at)) as Option<IProgram>;
const api = at ? await this._api.at(at) : this._api;
const programOption = (await api.query.gearProgram.programStorage(id)) as Option<IProgram>;

if (programOption.isNone) {
throw new ProgramDoesNotExistError(id);
Expand All @@ -42,12 +43,13 @@ export class GearProgramStorage {
* @param gProg
* @returns
*/
async getProgramPages(programId: HexString, program: ActiveProgram): Promise<IGearPages> {
async getProgramPages(programId: HexString, program: ActiveProgram, at?: HexString): Promise<IGearPages> {
const pages = {};
for (const page of program.pagesWithData) {
pages[page.toNumber()] = u8aToU8a(
await this._api.provider.send('state_getStorage', [
this._api.query.gearProgram.memoryPageStorage.key(programId, page),
at,
]),
);
}
Expand Down
Loading

0 comments on commit 04e4780

Please sign in to comment.