Skip to content

Commit 4cbb380

Browse files
Merge pull request #1069 from ProvableHQ/feat/program-version
[Feature] Add program versioning to the Network Client api
2 parents 63ffbf2 + 235e633 commit 4cbb380

File tree

3 files changed

+186
-45
lines changed

3 files changed

+186
-45
lines changed

docs/api_reference/sdk-src_network-client.md

Lines changed: 82 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -497,15 +497,17 @@ const latestHash = networkClient.getLatestBlockHash();
497497
498498
---
499499
500-
### `getProgram(programId) ► Promise.<string>`
500+
### `getProgram(programId, edition) ► Promise.<string>`
501501
502502
![modifier: public](images/badges/modifier-public.svg)
503503
504504
Returns the source code of a program given a program ID.
505505
506506
Parameters | Type | Description
507507
--- | --- | ---
508-
__programId__ | `string` | *The program ID of a program deployed to the Aleo Network*
508+
__programId__ | `string` | *The program ID of a program deployed to the Aleo Network.*
509+
__edition__ | `number` | *The edition of the program to fetch. When this is undefined it will fetch the latest version.*
510+
__*return*__ | `Promise.<string>` | *The source code of the program.*
509511
__*return*__ | `Promise.<string>` | *Source code of the program*
510512
511513
#### Examples
@@ -516,23 +518,57 @@ import { AleoNetworkClient } from "@provablehq/sdk/mainnet.js";
516518
// Create a network client.
517519
const networkClient = new AleoNetworkClient("http://api.explorer.provable.com/v1", undefined);
518520

521+
// Get the source code of a program.)
522+
```
523+
```javascript
524+
import { AleoNetworkClient } from "@provablehq/sdk/mainnet.js";
525+
526+
// Create a network client.
527+
const networkClient = new AleoNetworkClient("http://api.explorer.provable.com/v1", undefined);
528+
519529
const program = networkClient.getProgram("hello_hello.aleo");
520530
const expectedSource = "program hello_hello.aleo;\n\nfunction hello:\n input r0 as u32.public;\n input r1 as u32.private;\n add r0 r1 into r2;\n output r2 as u32.private;\n"
521531
assert.equal(program, expectedSource);
522532
```
523533
524534
---
525535
526-
### `getProgramObject(inputProgram) ► Promise.<Program>`
536+
### `getLatestProgramEdition(programId) ► Promise.<number>`
537+
538+
![modifier: public](images/badges/modifier-public.svg)
539+
540+
Returns the current program edition deployed on the Aleo network.
541+
542+
Parameters | Type | Description
543+
--- | --- | ---
544+
__programId__ | `string` | *The program ID of a program deployed to the Aleo Network.*
545+
__*return*__ | `Promise.<number>` | *The edition of the program.*
546+
547+
#### Examples
548+
549+
```javascript
550+
import { AleoNetworkClient } from "@provablehq/sdk/mainnet.js";
551+
552+
// Create a network client.
553+
const networkClient = new AleoNetworkClient("http://api.explorer.provable.com/v1", undefined);
554+
555+
const programVersion = networkClient.getLatestProgramEdition("hello_hello.aleo");
556+
assert.equal(programVersion, 1);
557+
```
558+
559+
---
560+
561+
### `getProgramObject(inputProgram, edition) ► Promise.<Program>`
527562
528563
![modifier: public](images/badges/modifier-public.svg)
529564
530565
Returns a program object from a program ID or program source code.
531566
532567
Parameters | Type | Description
533568
--- | --- | ---
534-
__inputProgram__ | `string` | *The program ID or program source code of a program deployed to the Aleo Network*
535-
__*return*__ | `Promise.<Program>` | *Source code of the program*
569+
__inputProgram__ | `string` | *The program ID or program source code of a program deployed to the Aleo Network.*
570+
__edition__ | `number` | *The edition of the program to fetch. When this is undefined it will fetch the latest version.*
571+
__*return*__ | `Promise.<Program>` | *Source code of the program.*
536572
537573
#### Examples
538574
@@ -1497,15 +1533,17 @@ const latestHash = networkClient.getLatestBlockHash();
14971533
14981534
---
14991535
1500-
### `getProgram(programId) ► Promise.<string>`
1536+
### `getProgram(programId, edition) ► Promise.<string>`
15011537
15021538
![modifier: public](images/badges/modifier-public.svg)
15031539
15041540
Returns the source code of a program given a program ID.
15051541
15061542
Parameters | Type | Description
15071543
--- | --- | ---
1508-
__programId__ | `string` | *The program ID of a program deployed to the Aleo Network*
1544+
__programId__ | `string` | *The program ID of a program deployed to the Aleo Network.*
1545+
__edition__ | `number` | *The edition of the program to fetch. When this is undefined it will fetch the latest version.*
1546+
__*return*__ | `Promise.<string>` | *The source code of the program.*
15091547
__*return*__ | `Promise.<string>` | *Source code of the program*
15101548
15111549
#### Examples
@@ -1516,23 +1554,57 @@ import { AleoNetworkClient } from "@provablehq/sdk/mainnet.js";
15161554
// Create a network client.
15171555
const networkClient = new AleoNetworkClient("http://api.explorer.provable.com/v1", undefined);
15181556

1557+
// Get the source code of a program.)
1558+
```
1559+
```javascript
1560+
import { AleoNetworkClient } from "@provablehq/sdk/mainnet.js";
1561+
1562+
// Create a network client.
1563+
const networkClient = new AleoNetworkClient("http://api.explorer.provable.com/v1", undefined);
1564+
15191565
const program = networkClient.getProgram("hello_hello.aleo");
15201566
const expectedSource = "program hello_hello.aleo;\n\nfunction hello:\n input r0 as u32.public;\n input r1 as u32.private;\n add r0 r1 into r2;\n output r2 as u32.private;\n"
15211567
assert.equal(program, expectedSource);
15221568
```
15231569
15241570
---
15251571
1526-
### `getProgramObject(inputProgram) ► Promise.<Program>`
1572+
### `getLatestProgramEdition(programId) ► Promise.<number>`
1573+
1574+
![modifier: public](images/badges/modifier-public.svg)
1575+
1576+
Returns the current program edition deployed on the Aleo network.
1577+
1578+
Parameters | Type | Description
1579+
--- | --- | ---
1580+
__programId__ | `string` | *The program ID of a program deployed to the Aleo Network.*
1581+
__*return*__ | `Promise.<number>` | *The edition of the program.*
1582+
1583+
#### Examples
1584+
1585+
```javascript
1586+
import { AleoNetworkClient } from "@provablehq/sdk/mainnet.js";
1587+
1588+
// Create a network client.
1589+
const networkClient = new AleoNetworkClient("http://api.explorer.provable.com/v1", undefined);
1590+
1591+
const programVersion = networkClient.getLatestProgramEdition("hello_hello.aleo");
1592+
assert.equal(programVersion, 1);
1593+
```
1594+
1595+
---
1596+
1597+
### `getProgramObject(inputProgram, edition) ► Promise.<Program>`
15271598
15281599
![modifier: public](images/badges/modifier-public.svg)
15291600
15301601
Returns a program object from a program ID or program source code.
15311602
15321603
Parameters | Type | Description
15331604
--- | --- | ---
1534-
__inputProgram__ | `string` | *The program ID or program source code of a program deployed to the Aleo Network*
1535-
__*return*__ | `Promise.<Program>` | *Source code of the program*
1605+
__inputProgram__ | `string` | *The program ID or program source code of a program deployed to the Aleo Network.*
1606+
__edition__ | `number` | *The edition of the program to fetch. When this is undefined it will fetch the latest version.*
1607+
__*return*__ | `Promise.<Program>` | *Source code of the program.*
15361608
15371609
#### Examples
15381610

sdk/src/network-client.ts

Lines changed: 85 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -890,7 +890,17 @@ class AleoNetworkClient {
890890
/**
891891
* Returns the source code of a program given a program ID.
892892
*
893-
* @param {string} programId The program ID of a program deployed to the Aleo Network
893+
* @param {string} programId The program ID of a program deployed to the Aleo Network.
894+
* @param {number | undefined} edition The edition of the program to fetch. When this is undefined it will fetch the latest version.
895+
* @returns {Promise<string>} The source code of the program.
896+
*
897+
* @example
898+
* import { AleoNetworkClient } from "@provablehq/sdk/mainnet.js";
899+
*
900+
* // Create a network client.
901+
* const networkClient = new AleoNetworkClient("http://api.explorer.provable.com/v1", undefined);
902+
*
903+
* // Get the source code of a program.)
894904
* @returns {Promise<string>} Source code of the program
895905
*
896906
* @example
@@ -903,22 +913,57 @@ class AleoNetworkClient {
903913
* const expectedSource = "program hello_hello.aleo;\n\nfunction hello:\n input r0 as u32.public;\n input r1 as u32.private;\n add r0 r1 into r2;\n output r2 as u32.private;\n"
904914
* assert.equal(program, expectedSource);
905915
*/
906-
async getProgram(programId: string): Promise<string> {
916+
async getProgram(programId: string, edition?: number): Promise<string> {
917+
try {
918+
this.ctx = { "X-ALEO-METHOD": "getProgramVersion" };
919+
if (typeof edition === "number") {
920+
return await this.fetchData<string>(
921+
`/program/${programId}/${edition}`,
922+
);
923+
} else {
924+
return await this.fetchData<string>("/program/" + programId);
925+
}
926+
} catch (error) {
927+
throw new Error(`Error fetching program ${programId}: ${error}`);
928+
} finally {
929+
this.ctx = {};
930+
}
931+
}
932+
933+
/**
934+
* Returns the current program edition deployed on the Aleo network.
935+
*
936+
* @param {string} programId The program ID of a program deployed to the Aleo Network.
937+
* @returns {Promise<number>} The edition of the program.
938+
*
939+
* @example
940+
* import { AleoNetworkClient } from "@provablehq/sdk/mainnet.js";
941+
*
942+
* // Create a network client.
943+
* const networkClient = new AleoNetworkClient("http://api.explorer.provable.com/v1", undefined);
944+
*
945+
* const programVersion = networkClient.getLatestProgramEdition("hello_hello.aleo");
946+
* assert.equal(programVersion, 1);
947+
*/
948+
async getLatestProgramEdition(programId: string): Promise<number> {
907949
try {
908-
this.ctx = { "X-ALEO-METHOD": "getProgram" };
909-
return await this.fetchData<string>("/program/" + programId);
950+
this.ctx = { "X-ALEO-METHOD": "getLatestProgramEdition" };
951+
return await this.fetchData<number>("/program/" + programId + "/latest_edition");
910952
} catch (error) {
911953
throw new Error(`Error fetching program ${programId}: ${error}`);
912954
} finally {
913955
this.ctx = {};
914956
}
915957
}
916958

959+
960+
917961
/**
918962
* Returns a program object from a program ID or program source code.
919963
*
920-
* @param {string} inputProgram The program ID or program source code of a program deployed to the Aleo Network
921-
* @returns {Promise<Program>} Source code of the program
964+
* @param {string} inputProgram The program ID or program source code of a program deployed to the Aleo Network.
965+
* @param {number | undefined} edition The edition of the program to fetch. When this is undefined it will fetch the latest version.
966+
* @returns {Promise<Program>} Source code of the program.
922967
*
923968
* @example
924969
* import { AleoNetworkClient } from "@provablehq/sdk/mainnet.js";
@@ -936,20 +981,16 @@ class AleoNetworkClient {
936981
* // Both program objects should be equal
937982
* assert(programObjectFromID.to_string() === programObjectFromSource.to_string());
938983
*/
939-
async getProgramObject(inputProgram: string): Promise<Program> {
984+
async getProgramObject(inputProgram: string, edition?: number): Promise<Program> {
940985
try {
941986
this.ctx = { "X-ALEO-METHOD": "getProgramObject" };
942-
return Program.fromString(inputProgram);
987+
return Program.fromString(
988+
<string>await this.getProgram(inputProgram, edition),
989+
);
943990
} catch (error) {
944-
try {
945-
return Program.fromString(
946-
<string>await this.getProgram(inputProgram),
947-
);
948-
} catch (error) {
949-
throw new Error(
950-
`${inputProgram} is neither a program name or a valid program: ${error}`,
951-
);
952-
}
991+
throw new Error(
992+
`${inputProgram} is neither a program name or a valid program: ${error}`,
993+
);
953994
} finally {
954995
this.ctx = {};
955996
}
@@ -985,40 +1026,49 @@ class AleoNetworkClient {
9851026
* programImports = await networkClient.getProgramImports(double_test);
9861027
* assert.deepStrictEqual(programImports, expectedImports);
9871028
*/
988-
async getProgramImports(
989-
inputProgram: Program | string,
990-
): Promise<ProgramImports> {
1029+
async getProgramImports(inputProgram: Program | string): Promise<ProgramImports> {
9911030
try {
9921031
this.ctx = { "X-ALEO-METHOD": "getProgramImports" };
9931032
const imports: ProgramImports = {};
9941033

995-
// Get the program object or fail if the program is not valid or does not exist
996-
const program =
997-
inputProgram instanceof Program
998-
? inputProgram
999-
: <Program>await this.getProgramObject(inputProgram);
1034+
// Normalize input to a Program object
1035+
let program: Program;
1036+
if (inputProgram instanceof Program) {
1037+
program = inputProgram;
1038+
} else {
1039+
try {
1040+
program = Program.fromString(inputProgram);
1041+
} catch {
1042+
try {
1043+
program = await this.getProgramObject(inputProgram);
1044+
} catch (error2) {
1045+
throw new Error(
1046+
`${inputProgram} is neither a program name nor a valid program: ${error2}`,
1047+
);
1048+
}
1049+
}
1050+
}
10001051

10011052
// Get the list of programs that the program imports
10021053
const importList = program.getImports();
10031054

1004-
// Recursively get any imports that the imported programs have in a depth first search order
1055+
// Recursively get any imports that the imported programs have in a depth-first search
10051056
for (let i = 0; i < importList.length; i++) {
10061057
const import_id = importList[i];
10071058
if (!imports.hasOwnProperty(import_id)) {
1008-
const programSource = <string>(
1009-
await this.getProgram(import_id)
1010-
);
1011-
const nestedImports = <ProgramImports>(
1012-
await this.getProgramImports(import_id)
1013-
);
1059+
const programSource = <string>await this.getProgram(import_id);
1060+
const nestedImports = <ProgramImports>await this.getProgramImports(import_id);
1061+
10141062
for (const key in nestedImports) {
10151063
if (!imports.hasOwnProperty(key)) {
10161064
imports[key] = nestedImports[key];
10171065
}
10181066
}
1067+
10191068
imports[import_id] = programSource;
10201069
}
10211070
}
1071+
10221072
return imports;
10231073
} catch (error: any) {
10241074
logAndThrow("Error fetching program imports: " + error.message);
@@ -1027,6 +1077,7 @@ class AleoNetworkClient {
10271077
}
10281078
}
10291079

1080+
10301081
/**
10311082
* Get a list of the program names that a program imports.
10321083
*
@@ -1094,7 +1145,7 @@ class AleoNetworkClient {
10941145
);
10951146
} catch (error) {
10961147
throw new Error(
1097-
`Error fetching mappings for program ${programId} - ensure the program exists on chain before trying again`,
1148+
`Error fetching mappings for program ${programId} - ensure the program exists on chain before trying again: ${error}`,
10981149
);
10991150
} finally {
11001151
this.ctx = {};
@@ -1133,7 +1184,7 @@ class AleoNetworkClient {
11331184
);
11341185
} catch (error) {
11351186
throw new Error(
1136-
`Error fetching value for key '${key}' in mapping '${mappingName}' in program '${programId}' - ensure the mapping exists and the key is correct`,
1187+
`Error fetching value for key '${key}' in mapping '${mappingName}' in program '${programId}' - ensure the mapping exists and the key is correct: ${error}`,
11371188
);
11381189
} finally {
11391190
this.ctx = {};

sdk/tests/network-client.test.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
} from "@provablehq/sdk/%%NETWORK%%.js";
2020
import { beaconPrivateKeyString } from "./data/account-data.js";
2121
import { retryWithBackoff } from "../src/utils.js";
22+
import { Program } from "@provablehq/wasm";
2223

2324
async function catchError(f: () => Promise<any>): Promise<Error | null> {
2425
try {
@@ -88,9 +89,26 @@ describe("NodeConnection", () => {
8889
});
8990

9091
describe("getProgram", () => {
91-
it("should return a string", async () => {
92+
it("endpoints return the correct types", async () => {
93+
// Ensure the program returned is a string.
9294
const program = await connection.getProgram("credits.aleo");
95+
const programV0 = await connection.getProgram("credits.aleo", 0);
96+
const programV1 = await connection.getProgram("credits.aleo", 1);
9397
expect(typeof program).equal("string");
98+
expect(typeof programV0).equal("string");
99+
expect(typeof programV1).equal("string");
100+
101+
// Ensure the program returned is of the correct object..
102+
const programWasm = await connection.getProgramObject("credits.aleo");
103+
const programWasmV0 = await connection.getProgramObject("credits.aleo", 0);
104+
const programWasmV1 = await connection.getProgramObject("credits.aleo", 1);
105+
expect(programWasm.id()).equals("credits.aleo");
106+
expect(programWasmV0.id()).equals("credits.aleo");
107+
expect(programWasmV1.id()).equals("credits.aleo");
108+
109+
// Ensure the edition returned is correct.
110+
const creditsEdition = await connection.getLatestProgramEdition("credits.aleo");
111+
expect(creditsEdition >= 1).to.equal(true);
94112
});
95113

96114
it("should throw an error if the request fails", async () => {

0 commit comments

Comments
 (0)