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

add session proxy calls and getters to worker-api #125

Merged
merged 15 commits into from
Dec 12, 2024
22 changes: 20 additions & 2 deletions packages/types/src/interfaces/integriteeWorker/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export default {
},
IntegriteeTrustedGetterSigned: {
getter: 'IntegriteeTrustedGetter',
delegate: 'Option<AccountId>',
signature: 'MultiSignature'
},
IntegriteeGetter: {
Expand All @@ -131,6 +132,7 @@ export default {
IntegriteeTrustedCallSigned: {
call: 'IntegriteeTrustedCall',
nonce: 'u32',
delegate: 'Option<AccountId>',
signature: 'MultiSignature'
},
IntegriteeTrustedCall: {
Expand All @@ -155,7 +157,7 @@ export default {
unused_index_17: null,
unused_index_18: null,
unused_index_19: null,
unused_index_20: null,
send_note: 'SendNoteArgs',
unused_index_21: null,
unused_index_22: null,
unused_index_23: null,
Expand All @@ -165,7 +167,7 @@ export default {
unused_index_27: null,
unused_index_28: null,
unused_index_29: null,
unused_index_30: null,
add_session_proxy: 'AddSessionProxyArgs',
unused_index_31: null,
unused_index_32: null,
unused_index_33: null,
Expand Down Expand Up @@ -231,6 +233,22 @@ export default {
AttemptsArg: {
origin: 'AccountId'
},
SessionProxyRole: {
_enum: {
readBalance: null,
readAny: null,
nonTransfer: null,
any: null,
transferAllowance: 'Balance'
}
},
SessionProxyCredentials: {
role: 'SessionProxyRole',
expiry: 'Option<Moment>',
seed: 'H256',
},
AddSessionProxyArgs: '(AccountId, AccountId, SessionProxyCredentials)',
SendNoteArgs: '(AccountId, AccountId, String)',
GuessTheNumberTrustedCall: {
_enum: {
set_winnings: 'GuessTheNumberSetWinningsArgs',
Expand Down
36 changes: 33 additions & 3 deletions packages/types/src/interfaces/integriteeWorker/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { ParentchainId, ShardIdentifier } from '@encointer/types/interfaces
import type { Bytes, Enum, Option, Struct, Text, Vec, u16, u32, u64 } from '@polkadot/types-codec';
import type { ITuple } from '@polkadot/types-codec/types';
import type { MultiSignature } from '@polkadot/types/interfaces/extrinsics';
import type { AccountId, Balance, H160, Moment } from '@polkadot/types/interfaces/runtime';
import type {AccountId, Balance, H160, Moment} from '@polkadot/types/interfaces/runtime';

/** @name AttemptsArg */
export interface AttemptsArg extends Struct {
Expand All @@ -22,6 +22,30 @@ export interface BalanceTransferArgs extends ITuple<[AccountId, AccountId, Balan
/** @name BalanceTransferWithNoteArgs */
export interface BalanceTransferWithNoteArgs extends ITuple<[AccountId, AccountId, BalanceType, Text]> {}

/** @name SendNoteArgs */
export interface SendNoteArgs extends ITuple<[AccountId, AccountId, Text]> {}

/** @name AddSessionProxyArgs */
export interface AddSessionProxyArgs extends ITuple<[AccountId, AccountId, SessionProxyCredentials]> {}

/** @name SessionProxyRole */
export interface SessionProxyRole extends Enum {
readonly isReadBalance: boolean;
readonly isReadAny: boolean;
readonly isNonTransfer: boolean;
readonly isAny: boolean;
readonly isTransferAllowance: boolean;
readonly asTransferAllowance: Balance;
readonly type: 'ReadBalance' | 'ReadAny' | 'NonTransfer' | 'Any' | 'TransferAllowance';
}

/** @name SessionProxyCredentials */
export interface SessionProxyCredentials extends Struct {
readonly role: SessionProxyRole;
readonly expiry: Option<Moment>;
readonly seed: Uint8Array;
}

/** @name BalanceUnshieldArgs */
export interface BalanceUnshieldArgs extends ITuple<[AccountId, AccountId, BalanceType, ShardIdentifier]> {}

Expand Down Expand Up @@ -174,7 +198,8 @@ export interface IntegriteeTrustedCall extends Enum {
readonly isUnusedIndex17: boolean;
readonly isUnusedIndex18: boolean;
readonly isUnusedIndex19: boolean;
readonly isUnusedIndex20: boolean;
readonly isSendNote: boolean;
readonly asSendNote: SendNoteArgs;
readonly isUnusedIndex21: boolean;
readonly isUnusedIndex22: boolean;
readonly isUnusedIndex23: boolean;
Expand All @@ -184,7 +209,8 @@ export interface IntegriteeTrustedCall extends Enum {
readonly isUnusedIndex27: boolean;
readonly isUnusedIndex28: boolean;
readonly isUnusedIndex29: boolean;
readonly isUnusedIndex30: boolean;
readonly isAddSessionProxy: boolean;
readonly asAddSessionProxy: AddSessionProxyArgs;
readonly isUnusedIndex31: boolean;
readonly isUnusedIndex32: boolean;
readonly isUnusedIndex33: boolean;
Expand Down Expand Up @@ -213,13 +239,16 @@ export interface IntegriteeTrustedCall extends Enum {
export interface IntegriteeTrustedCallSigned extends Struct {
readonly call: IntegriteeTrustedCall;
readonly nonce: u32;
readonly delegate: Option<AccountId>,
readonly signature: MultiSignature;
}

/** @name IntegriteeTrustedGetter */
export interface IntegriteeTrustedGetter extends Enum {
readonly isAccountInfo: boolean;
readonly asAccountInfo: AccountId;
readonly isAccountInfoAndSessionProxies: boolean;
readonly asAccountInfoAndSessionProxies: AccountId;
readonly isUnusedIndex1: boolean;
readonly isUnusedIndex2: boolean;
readonly isUnusedIndex3: boolean;
Expand Down Expand Up @@ -278,6 +307,7 @@ export interface IntegriteeTrustedGetter extends Enum {
/** @name IntegriteeTrustedGetterSigned */
export interface IntegriteeTrustedGetterSigned extends Struct {
readonly getter: IntegriteeTrustedGetter;
readonly delegate: Option<AccountId>
readonly signature: MultiSignature;
}

Expand Down
108 changes: 105 additions & 3 deletions packages/worker-api/src/integriteeWorker.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Keyring } from '@polkadot/api';
import { cryptoWaitReady } from '@polkadot/util-crypto';
import {cryptoWaitReady, mnemonicToMiniSecret} from '@polkadot/util-crypto';
import {localDockerNetwork} from './testUtils/networks.js';
import { IntegriteeWorker } from './integriteeWorker.js';
import {type KeyringPair} from "@polkadot/keyring/types";

import WS from 'websocket';
import type {AccountInfo} from "@polkadot/types/interfaces/system";

const {w3cwebsocket: WebSocket} = WS;

Expand Down Expand Up @@ -78,11 +79,25 @@ describe('worker', () => {

describe('accountInfoGetter', () => {
it('should return value', async () => {
const getter = await worker.accountInfoGetter(charlie, network.shard);
const getter = await worker.accountInfoGetter(alice, network.shard);
console.log(`AccountInfoGetter: ${JSON.stringify(getter)}`);
const result = await getter.send();
console.log('getAccountInfo:', result.toHuman());
expect(result).toBeDefined();
const info = result as AccountInfo;
expect(info.data.free.toBigInt()).toBeGreaterThan(0);
});

it('should fall back to default if signed by unauthorized delegate', async () => {
const getter = await worker.accountInfoGetter(alice, network.shard, { delegate: charlie });
console.log(`AccountInfoGetter with unauthorized signature: ${JSON.stringify(getter)}`);
const result = await getter.send();
console.log('getAccountInfo:', result.toHuman());
expect(result).toBeDefined();
const info = result as AccountInfo;
console.log("parsed: ", info.data.free);
// we don't forward errors here. instead, failures are mapped to default, which is zero
expect(info.data.free.toBigInt()).toEqual(BigInt(0));
Comment on lines +96 to +100
Copy link
Member Author

Choose a reason for hiding this comment

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

toBeDefined is used too often IMHO. It says almost nothing about success

Copy link
Member

Choose a reason for hiding this comment

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

Yes, but I did this as I wanted to test it against a running setup that might return different results for sequential runs.

});
});

Expand Down Expand Up @@ -136,9 +151,32 @@ describe('worker', () => {
});
});

describe('should return note of the executed trusted call', () => {
describe('call signed by unauthorized delegate should fail', () => {
it('should fail', async () => {
const shard = network.shard;
//const testNote = "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678";
const testNote = "My test note";
const result = await worker.trustedBalanceTransfer(
alice,
shard,
network.mrenclave,
alice.address,
charlie.address,
1100000000000,
testNote,
{ delegate: charlie}
);
console.log('balance transfer result', JSON.stringify(result));
expect(result).toBeDefined();
const status = worker.createType('TrustedOperationStatus', result.status);
expect(status.isInvalid).toBeTruthy();
});
});

describe.skip('should return note of the executed trusted call', () => {
it('should return balance transfer with note as note', async () => {
const shard = network.shard;
//const testNote = "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678";
const testNote = "My test note";
const result = await worker.trustedBalanceTransfer(
alice,
Expand Down Expand Up @@ -172,6 +210,70 @@ describe('worker', () => {
});
});

// race condition so skipped
describe.skip('session proxies (delegates) should work', () => {
it('add delegate should work', async () => {
const shard = network.shard;
const now = new Date();
const expiryDate = new Date(now.getTime() + 40 * 24 * 60 * 60 * 1000);
const expiry = Math.floor(expiryDate.getTime());
const miniSecret = mnemonicToMiniSecret("secret forest ticket smooth wide mass parent reveal embark impose fiscal company");
const role = worker.createType('SessionProxyRole', 'Any');
const result = await worker.trustedAddSessionProxy(
alice,
shard,
network.mrenclave,
role,
'5DwH48esFAmQWjaae7zvzzAbhRgS4enS7tfUPTbGr6ZFnW7R',
expiry,
miniSecret,
);
console.log('add session proxy', JSON.stringify(result));
expect(result).toBeDefined();
});

it('call as delegate should work', async () => {
const shard = network.shard;
const localKeyring = new Keyring({ type: "sr25519", ss58Format: 42 });
const delegate = localKeyring.addFromMnemonic("secret forest ticket smooth wide mass parent reveal embark impose fiscal company", {
name: "fresh",
});
const result = await worker.trustedBalanceTransfer(
alice,
shard,
network.mrenclave,
alice.address,
'5DwH48esFAmQWjaae7zvzzAbhRgS4enS7tfUPTbGr6ZFnW7R',
1100000000000,
"My test note",
{ delegate: delegate }
);
console.log('delegated balance transfer result', JSON.stringify(result));
expect(result).toBeDefined();
const status = worker.createType('TrustedOperationStatus', result.status);
expect(status.isInSidechainBlock).toBeTruthy();
});
});

// race condition so skipped
describe.skip('send note should work', () => {
it('send note included', async () => {
const shard = network.shard;
const result = await worker.trustedSendNote(
alice,
shard,
network.mrenclave,
alice.address,
charlie.address,
"Hoi"
);
console.log('send note', JSON.stringify(result));
expect(result).toBeDefined();
const status = worker.createType('TrustedOperationStatus', result.status);
expect(status.isInSidechainBlock).toBeTruthy();
});
});

// race condition so skipped
describe.skip('balance transfer should work', () => {
it('should return value', async () => {
Expand Down
44 changes: 42 additions & 2 deletions packages/worker-api/src/integriteeWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type {
GuessTheNumberTrustedGetter,
AttemptsArg,
ParentchainsInfo,
NotesBucketInfo, TimestampedTrustedNote,
NotesBucketInfo, TimestampedTrustedNote, SessionProxyRole,
} from '@encointer/types';
import {
type ISubmittableGetter,
Expand Down Expand Up @@ -47,6 +47,7 @@ export class IntegriteeWorker extends Worker {
const trustedGetterArgs = {
shard: shard,
account: accountOrPubKey,
delegate: signerOptions?.delegate,
signer: signerOptions?.signer,
}
return await submittableTrustedGetter<IntegriteeWorker, AccountInfo>(this, 'account_info', accountOrPubKey, trustedGetterArgs, asString(accountOrPubKey), 'AccountInfo');
Expand Down Expand Up @@ -140,6 +141,45 @@ export class IntegriteeWorker extends Worker {
return this.sendTrustedCall(signed, shardT);
}

public async trustedAddSessionProxy(
account: AddressOrPair,
shard: string,
mrenclave: string,
role: SessionProxyRole,
delegate: AddressOrPair,
expiry: number,
seed: Uint8Array,
signerOptions?: TrustedSignerOptions,
): Promise<TrustedCallResult> {
const nonce = signerOptions?.nonce ?? await this.getNonce(account, shard, signerOptions)

const shardT = this.createType('ShardIdentifier', bs58.decode(shard));
const credentials = this.createType('SessionProxyCredentials', [role, expiry, seed])
const params = this.createType('AddSessionProxyArgs', [asString(account), asString(delegate), credentials])
const call = createTrustedCall(this, ['add_session_proxy', 'AddSessionProxyArgs'], params);
const signed = await signTrustedCall(this, call, account, shardT, mrenclave, nonce, signerOptions);

console.debug(`AddSessionProxy ${JSON.stringify(signed)}`);
return this.sendTrustedCall(signed, shardT);
}

public async trustedSendNote(
account: AddressOrPair,
shard: string,
mrenclave: string,
from: String,
to: String,
note: string,
signerOptions?: TrustedSignerOptions,
): Promise<TrustedCallResult> {
const nonce = signerOptions?.nonce ?? await this.getNonce(account, shard, signerOptions)
const shardT = this.createType('ShardIdentifier', bs58.decode(shard));
const params = this.createType('SendNoteArgs', [from, to, note])
const call = createTrustedCall(this, ['send_note', 'SendNoteArgs'], params);
const signed = await signTrustedCall(this, call, account, shardT, mrenclave, nonce, signerOptions);
return this.sendTrustedCall(signed, shardT);
}

public async guessTheNumber(
account: AddressOrPair,
shard: string,
Expand Down Expand Up @@ -194,7 +234,7 @@ export class SubmittableGetter<W extends Worker, Type> implements ISubmittableGe
async function submittableTrustedGetter<W extends Worker, T>(self: W, request: string, account: AddressOrPair, args: TrustedGetterArgs, trustedGetterParams: TrustedGetterParams, returnType: string): Promise<SubmittableGetter<W, T>> {
const {shard} = args;
const shardT = self.createType('ShardIdentifier', bs58.decode(shard));
const signedGetter = await createSignedGetter(self, request, account, trustedGetterParams, { signer: args?.signer })
const signedGetter = await createSignedGetter(self, request, account, trustedGetterParams, { signer: args?.signer, delegate: args?.delegate });
return new SubmittableGetter<W, T>(self, shardT, signedGetter, returnType);
}

Expand Down
6 changes: 5 additions & 1 deletion packages/worker-api/src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export interface WorkerOptions {
export interface TrustedGetterArgs {
shard: string;
account: AddressOrPair;
delegate?: AddressOrPair;
signer?: Signer
}

Expand All @@ -65,9 +66,12 @@ export type TrustedGetterParams = string | GuessTheNumberTrustedGetter | null
* In the future, this might include other things.
*/
export interface TrustedSignerOptions {
// If this is null, we assume that the account is a Pair.
// use signer extension? If this is null, we assume that the account is a Pair.
signer?: Signer;

// use session proxy pair?
delegate?: AddressOrPair

// If the nonce is null, it will be fetched.
nonce?: u32;
}
Expand Down
Loading