Skip to content

Commit

Permalink
[Feature] Wasm Binary Size & Multi-Threading Optimization
Browse files Browse the repository at this point in the history
  • Loading branch information
iamalwaysuncomfortable authored Oct 16, 2023
2 parents 74790fd + 18b900f commit 4c221a0
Show file tree
Hide file tree
Showing 23 changed files with 1,997 additions and 2,197 deletions.
29 changes: 27 additions & 2 deletions create-aleo-app/template-node/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Account, initThreadPool, PrivateKey, ProgramManager,} from "@aleohq/sdk";
import {Account, initThreadPool, ProgramManager, AleoKeyProvider, AleoKeyProviderParams} from "@aleohq/sdk";

await initThreadPool();

Expand All @@ -18,11 +18,36 @@ async function localProgramExecution(program, aleoFunction, inputs) {
const account = new Account();
programManager.setAccount(account);

const executionResponse = await programManager.executeOffline(
// Create a key provider in order to re-use the same key for each execution
const keyProvider = new AleoKeyProvider();
programManager.setKeyProvider(keyProvider);

// Pre-synthesize the program keys and then cache them in memory using key provider
const keyPair = await programManager.synthesizeKeys(hello_hello_program, "hello", ["1u32", "1u32"]);
programManager.keyProvider.cacheKeys("hello_hello.aleo:hello", keyPair);

// Specify parameters for the key provider to use search for program keys. In particular specify the cache key
// that was used to cache the keys in the previous step.
const keyProviderParams = new AleoKeyProviderParams({cacheKey: "hello_hello.aleo:hello"});

// Execute once using the key provider params defined above. This will use the cached proving keys and make
// execution significantly faster.
let executionResponse = await programManager.executeOffline(
hello_hello_program,
"hello",
["5u32", "5u32"],
false,
undefined,
keyProviderParams,
);
console.log(executionResponse.getOutputs())

executionResponse = await programManager.executeOffline(
hello_hello_program,
"hello",
["5u32", "5u32"],
false,
keyProviderParams,
);
return executionResponse.getOutputs();
}
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
{
"name": "sdk-root",
"private": true,
"version": "0.0.0",
Expand All @@ -14,6 +14,8 @@
"build:sdk": "cd sdk && yarn build",
"build:all": "yarn build:wasm && yarn build:sdk",

"start:website": "cd website && yarn dev",

"test:wasm": "cd wasm && yarn test",
"test:sdk": "cd sdk && yarn test",
"test": "yarn test:wasm && yarn test:sdk",
Expand Down
58 changes: 50 additions & 8 deletions sdk/src/program-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ class ProgramManager {
host: string;
networkClient: AleoNetworkClient;
recordProvider: RecordProvider | undefined;
executionEngine: WasmProgramManager;

/** Create a new instance of the ProgramManager
*
Expand All @@ -54,7 +53,6 @@ class ProgramManager {
this.keyProvider = keyProvider;
}

this.executionEngine = new WasmProgramManager();
this.recordProvider = recordProvider;
}

Expand Down Expand Up @@ -186,7 +184,7 @@ class ProgramManager {
}

// Build a deployment transaction and submit it to the network
const tx = await this.executionEngine.buildDeploymentTransaction(deploymentPrivateKey, program, fee, feeRecord, this.host, false, imports, feeProvingKey, feeVerifyingKey);
const tx = await WasmProgramManager.buildDeploymentTransaction(deploymentPrivateKey, program, fee, feeRecord, this.host, imports, feeProvingKey, feeVerifyingKey);
return await this.networkClient.submitTransaction(tx);
}

Expand Down Expand Up @@ -287,7 +285,7 @@ class ProgramManager {
}

// Build an execution transaction and submit it to the network
const tx = await this.executionEngine.buildExecutionTransaction(executionPrivateKey, program, functionName, inputs, fee, feeRecord, this.host, false, imports, provingKey, verifyingKey, feeProvingKey, feeVerifyingKey);
const tx = await WasmProgramManager.buildExecutionTransaction(executionPrivateKey, program, functionName, inputs, fee, feeRecord, this.host, imports, provingKey, verifyingKey, feeProvingKey, feeVerifyingKey);
return await this.networkClient.submitTransaction(tx);
}

Expand Down Expand Up @@ -357,7 +355,7 @@ class ProgramManager {
console.log("Running program offline")
console.log("Proving key: ", provingKey);
console.log("Verifying key: ", verifyingKey);
return this.executionEngine.executeFunctionOffline(executionPrivateKey, program, function_name, inputs, proveExecution,false, imports, provingKey, verifyingKey);
return WasmProgramManager.executeFunctionOffline(executionPrivateKey, program, function_name, inputs, proveExecution, false, imports, provingKey, verifyingKey);
}

/**
Expand Down Expand Up @@ -420,7 +418,7 @@ class ProgramManager {
}

// Build an execution transaction and submit it to the network
const tx = await this.executionEngine.buildJoinTransaction(executionPrivateKey, recordOne, recordTwo, fee, feeRecord, this.host, false, joinProvingKey, joinVerifyingKey, feeProvingKey, feeVerifyingKey);
const tx = await WasmProgramManager.buildJoinTransaction(executionPrivateKey, recordOne, recordTwo, fee, feeRecord, this.host, joinProvingKey, joinVerifyingKey, feeProvingKey, feeVerifyingKey);
return await this.networkClient.submitTransaction(tx);
}

Expand Down Expand Up @@ -473,10 +471,54 @@ class ProgramManager {
}

// Build an execution transaction and submit it to the network
const tx = await this.executionEngine.buildSplitTransaction(executionPrivateKey, splitAmount, amountRecord, this.host, false, splitProvingKey, splitVerifyingKey);
const tx = await WasmProgramManager.buildSplitTransaction(executionPrivateKey, splitAmount, amountRecord, this.host, splitProvingKey, splitVerifyingKey);
return await this.networkClient.submitTransaction(tx);
}

/**
* Pre-synthesize proving and verifying keys for a program
*
* @param program {string} The program source code to synthesize keys for
* @param function_id {string} The function id to synthesize keys for
* @param inputs {Array<string>} Sample inputs to the function
* @param privateKey {PrivateKey | undefined} Optional private key to use for the key synthesis
*
* @returns {Promise<FunctionKeyPair | Error>}
*/
async synthesizeKeys(
program: string,
function_id: string,
inputs: Array<string>,
privateKey?: PrivateKey,
): Promise<FunctionKeyPair | Error> {
// Resolve the program imports if they exist
let imports;

let executionPrivateKey = privateKey;
if (typeof executionPrivateKey === "undefined") {
if (typeof this.account !== "undefined") {
executionPrivateKey = this.account.privateKey();
} else {
executionPrivateKey = new PrivateKey();
}
}

// Attempt to run an offline execution of the program and extract the proving and verifying keys
try {
imports = await this.networkClient.getProgramImports(program);
const keyPair = await WasmProgramManager.synthesizeKeyPair(
executionPrivateKey,
program,
function_id,
inputs,
imports
);
return [<VerifyingKey>keyPair.provingKey(), <ProvingKey>keyPair.verifyingKey()];
} catch (e) {
throw logAndThrow(`Could not synthesize keys - error ${e}. Please ensure the program is valid and the inputs are correct.`);
}
}

/**
* Transfer credits to another account
*
Expand Down Expand Up @@ -549,7 +591,7 @@ class ProgramManager {
}

// Build an execution transaction and submit it to the network
const tx = await this.executionEngine.buildTransferTransaction(executionPrivateKey, amount, recipient, transferType, amountRecord, fee, feeRecord, this.host, false, transferProvingKey, transferVerifyingKey, feeProvingKey, feeVerifyingKey);
const tx = await WasmProgramManager.buildTransferTransaction(executionPrivateKey, amount, recipient, transferType, amountRecord, fee, feeRecord, this.host, transferProvingKey, transferVerifyingKey, feeProvingKey, feeVerifyingKey);
return await this.networkClient.submitTransaction(tx);
}

Expand Down
19 changes: 9 additions & 10 deletions sdk/src/worker.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { initThreadPool, ProgramManager, PrivateKey, verifyFunctionExecution } from "./index";
import {initThreadPool, ProgramManager, PrivateKey, verifyFunctionExecution, FunctionKeyPair} from "./index";
import { AleoKeyProvider, AleoKeyProviderParams} from "./function-key-provider";
import { expose } from "comlink";

Expand Down Expand Up @@ -31,7 +31,7 @@ async function executeOffline(
aleoFunction: string,
inputs: string[],
privateKey: string,
proveExecution: boolean = false
proveExecution = false
) {
console.log("Web worker: Executing function locally...");
let startTime = performance.now();
Expand All @@ -58,14 +58,13 @@ async function executeOffline(
}
// Get the proving and verifying keys for the function
if (lastLocalProgram !== localProgram) {
const keys = programManager.executionEngine.synthesizeKeypair(
const keys = <FunctionKeyPair>await programManager.synthesizeKeys(
localProgram,
aleoFunction
aleoFunction,
inputs,
PrivateKey.from_string(privateKey)
);
programManager.keyProvider.cacheKeys(cacheKey, [
keys.provingKey(),
keys.verifyingKey(),
]);
programManager.keyProvider.cacheKeys(cacheKey, keys);
lastLocalProgram = localProgram;
}

Expand Down Expand Up @@ -94,10 +93,10 @@ async function executeOffline(
} ms`
);
const outputs = response.getOutputs();
let execution = response.getExecution();
const execution = response.getExecution();
let executionString = "";

let keys = keyProvider.getKeys(cacheKey);
const keys = keyProvider.getKeys(cacheKey);

if (keys instanceof Error) {
throw "Could not get verifying key";
Expand Down
2 changes: 1 addition & 1 deletion sdk/tests/key-provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {AleoKeyProvider, CachedKeyPair, CREDITS_PROGRAM_KEYS, FunctionKeyPair, P
import {jest} from '@jest/globals'
jest.retryTimes(3);

describe.skip('KeyProvider', () => {
describe('KeyProvider', () => {
let keyProvider: AleoKeyProvider;

beforeEach(() => {
Expand Down
11 changes: 0 additions & 11 deletions sdk/tests/network-client.integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,16 +77,5 @@ describe('NodeConnection', () => {
}

}, 60000);

it.skip('should find finalize scope values', async () => {
const mappings = await localApiClient.getProgramMappingNames("credits.aleo");
if (!(mappings instanceof Error)) {
expect(mappings).toBe(["committee", "bonded", "unbonding", "account"]);
}
const mappingValue = await localApiClient.getProgramMappingValue("credits.aleo", "account", beaconAddressString);
if (!(mappingValue instanceof Error)) {
expect(mappingValue).toBe("3000000u64");
}
}, 60000);
});
});
27 changes: 19 additions & 8 deletions sdk/tests/network-client.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import {jest} from '@jest/globals'
import {Account, Block, AleoNetworkClient, Transaction} from "../src/node";
import {beaconPrivateKeyString} from "./data/account-data";
import {beaconAddressString, beaconPrivateKeyString} from "./data/account-data";
import {log} from "console";
jest.retryTimes(3);

describe.skip('NodeConnection', () => {
describe('NodeConnection', () => {
let connection: AleoNetworkClient;

beforeEach(() => {
connection = new AleoNetworkClient("https://vm.aleo.org/api");
connection = new AleoNetworkClient("https://api.explorer.aleo.org/v1");
});

describe('setAccount', () => {
Expand All @@ -22,7 +22,7 @@ describe.skip('NodeConnection', () => {
describe('getBlock', () => {
it('should return a Block object', async () => {
const block = await connection.getBlock(1);
expect((block as Block).block_hash).toEqual("ab1sj5ecvzuvpzev8s8ukx2l5wqzxetakt7wh6ck6fldxldecyugvyqa0jgm2");
expect((block as Block).block_hash).toEqual("ab1n79nyqnxa76wpz40efqlq53artsw86wrez4tw9kn5xrpuc65xyxquh3wnw");
}, 60000);

it('should throw an error if the request fails', async () => {
Expand Down Expand Up @@ -135,13 +135,12 @@ describe.skip('NodeConnection', () => {
expect(records.length).toBe(0);
}
}, 90000);

});

describe('getProgramImports', () => {
it.skip('should return the correct program import names', async () => {
const importNames = await connection.getProgramImportNames("imported_add_mul.aleo");
const expectedNames = ["double_test.aleo", "addition_test.aleo"];
it('should return the correct program import names', async () => {
const importNames = await connection.getProgramImportNames("disperse_multiple_transactions.aleo");
const expectedNames = ["credits.aleo"];
expect(importNames).toEqual(expectedNames);

const creditImports = await connection.getProgramImportNames("credits.aleo");
Expand Down Expand Up @@ -178,4 +177,16 @@ describe.skip('NodeConnection', () => {
expect(imports).toEqual(expectedImports);
}, 60000);
});
describe('Mappings', () => {
it('should find program mappings and read mappings', async () => {
const mappings = await connection.getProgramMappingNames("credits.aleo");
if (!(mappings instanceof Error)) {
expect(mappings).toEqual(["committee", "bonded", "unbonding", "account"]);
}
const mappingValue = await connection.getProgramMappingValue("credits.aleo", "account", "aleo1rlwt9w0fl242h40w454m68vttd6vm4lmetu5r57unm5g354y9yzsyexf0y");
if (!(mappingValue instanceof Error)) {
expect(mappingValue).toBeTruthy()
}
}, 60000);
});
});
Loading

0 comments on commit 4c221a0

Please sign in to comment.