@aztec/txe 0.0.0-test.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dest/bin/index.d.ts +3 -0
  2. package/dest/bin/index.d.ts.map +1 -0
  3. package/dest/bin/index.js +30 -0
  4. package/dest/index.d.ts +8 -0
  5. package/dest/index.d.ts.map +1 -0
  6. package/dest/index.js +156 -0
  7. package/dest/node/txe_node.d.ts +358 -0
  8. package/dest/node/txe_node.d.ts.map +1 -0
  9. package/dest/node/txe_node.js +504 -0
  10. package/dest/oracle/txe_oracle.d.ts +152 -0
  11. package/dest/oracle/txe_oracle.d.ts.map +1 -0
  12. package/dest/oracle/txe_oracle.js +833 -0
  13. package/dest/txe_service/txe_service.d.ts +212 -0
  14. package/dest/txe_service/txe_service.d.ts.map +1 -0
  15. package/dest/txe_service/txe_service.js +572 -0
  16. package/dest/util/encoding.d.ts +103 -0
  17. package/dest/util/encoding.d.ts.map +1 -0
  18. package/dest/util/encoding.js +76 -0
  19. package/dest/util/expected_failure_error.d.ts +4 -0
  20. package/dest/util/expected_failure_error.d.ts.map +1 -0
  21. package/dest/util/expected_failure_error.js +5 -0
  22. package/dest/util/txe_account_data_provider.d.ts +10 -0
  23. package/dest/util/txe_account_data_provider.d.ts.map +1 -0
  24. package/dest/util/txe_account_data_provider.js +17 -0
  25. package/dest/util/txe_public_contract_data_source.d.ts +20 -0
  26. package/dest/util/txe_public_contract_data_source.d.ts.map +1 -0
  27. package/dest/util/txe_public_contract_data_source.js +88 -0
  28. package/dest/util/txe_world_state_db.d.ts +14 -0
  29. package/dest/util/txe_world_state_db.d.ts.map +1 -0
  30. package/dest/util/txe_world_state_db.js +27 -0
  31. package/package.json +93 -0
  32. package/src/bin/index.ts +39 -0
  33. package/src/index.ts +213 -0
  34. package/src/node/txe_node.ts +725 -0
  35. package/src/oracle/txe_oracle.ts +1241 -0
  36. package/src/txe_service/txe_service.ts +749 -0
  37. package/src/util/encoding.ts +92 -0
  38. package/src/util/expected_failure_error.ts +5 -0
  39. package/src/util/txe_account_data_provider.ts +23 -0
  40. package/src/util/txe_public_contract_data_source.ts +101 -0
  41. package/src/util/txe_world_state_db.ts +38 -0
@@ -0,0 +1,76 @@
1
+ import { Fr } from '@aztec/foundation/fields';
2
+ import { hexToBuffer } from '@aztec/foundation/string';
3
+ import { ContractArtifactSchema } from '@aztec/stdlib/abi';
4
+ import { AztecAddress } from '@aztec/stdlib/aztec-address';
5
+ import { ContractInstanceWithAddressSchema } from '@aztec/stdlib/contract';
6
+ import { z } from 'zod';
7
+ export function fromSingle(obj) {
8
+ return Fr.fromBuffer(Buffer.from(obj, 'hex'));
9
+ }
10
+ export function addressFromSingle(obj) {
11
+ return new AztecAddress(fromSingle(obj));
12
+ }
13
+ export function fromArray(obj) {
14
+ return obj.map((str)=>Fr.fromBuffer(hexToBuffer(str)));
15
+ }
16
+ /**
17
+ * Converts an array of Noir unsigned integers to a single tightly-packed buffer.
18
+ * @param uintBitSize If it's an array of Noir u8's, put `8`, etc.
19
+ * @returns
20
+ */ export function fromUintArray(obj, uintBitSize) {
21
+ if (uintBitSize % 8 !== 0) {
22
+ throw new Error(`u${uintBitSize} is not a supported type in Noir`);
23
+ }
24
+ const uintByteSize = uintBitSize / 8;
25
+ return Buffer.concat(obj.map((str)=>hexToBuffer(str).slice(-uintByteSize)));
26
+ }
27
+ export function toSingle(obj) {
28
+ return obj.toString().slice(2);
29
+ }
30
+ export function toArray(objs) {
31
+ return objs.map((obj)=>obj.toString());
32
+ }
33
+ export function bufferToU8Array(buffer) {
34
+ return toArray(Array.from(buffer).map((byte)=>new Fr(byte)));
35
+ }
36
+ /**
37
+ * Converts a ForeignCallArray into a tuple which represents a nr BoundedVec.
38
+ * If the input array is shorter than the maxLen, it pads the result with zeros,
39
+ * so that nr can correctly coerce this result into a BoundedVec.
40
+ * @param array
41
+ * @param maxLen - the max length of the BoundedVec.
42
+ * @returns a tuple representing a BoundedVec.
43
+ */ export function arrayToBoundedVec(array, maxLen) {
44
+ if (array.length > maxLen) {
45
+ throw new Error(`Array of length ${array.length} larger than maxLen ${maxLen}`);
46
+ }
47
+ const lengthDiff = maxLen - array.length;
48
+ // We pad the array to the maxLen of the BoundedVec.
49
+ const zeroPaddingArray = toArray(Array(lengthDiff).fill(new Fr(0)));
50
+ // These variable names match with the BoundedVec members in nr:
51
+ const storage = array.concat(zeroPaddingArray);
52
+ const len = toSingle(new Fr(array.length));
53
+ return [
54
+ storage,
55
+ len
56
+ ];
57
+ }
58
+ export function toForeignCallResult(obj) {
59
+ return {
60
+ values: obj
61
+ };
62
+ }
63
+ export const ForeignCallSingleSchema = z.string();
64
+ export const ForeignCallArraySchema = z.array(z.string());
65
+ export const ForeignCallArgsSchema = z.array(z.union([
66
+ ForeignCallSingleSchema,
67
+ ForeignCallArraySchema,
68
+ ContractArtifactSchema,
69
+ ContractInstanceWithAddressSchema
70
+ ]));
71
+ export const ForeignCallResultSchema = z.object({
72
+ values: z.array(z.union([
73
+ ForeignCallSingleSchema,
74
+ ForeignCallArraySchema
75
+ ]))
76
+ });
@@ -0,0 +1,4 @@
1
+ export declare class ExpectedFailureError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ //# sourceMappingURL=expected_failure_error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expected_failure_error.d.ts","sourceRoot":"","sources":["../../src/util/expected_failure_error.ts"],"names":[],"mappings":"AAAA,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,OAAO,EAAE,MAAM;CAG5B"}
@@ -0,0 +1,5 @@
1
+ export class ExpectedFailureError extends Error {
2
+ constructor(message){
3
+ super(message);
4
+ }
5
+ }
@@ -0,0 +1,10 @@
1
+ import type { AztecAsyncKVStore } from '@aztec/kv-store';
2
+ import type { AztecAddress } from '@aztec/stdlib/aztec-address';
3
+ import { CompleteAddress } from '@aztec/stdlib/contract';
4
+ export declare class TXEAccountDataProvider {
5
+ #private;
6
+ constructor(store: AztecAsyncKVStore);
7
+ getAccount(key: AztecAddress): Promise<CompleteAddress>;
8
+ setAccount(key: AztecAddress, value: CompleteAddress): Promise<void>;
9
+ }
10
+ //# sourceMappingURL=txe_account_data_provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"txe_account_data_provider.d.ts","sourceRoot":"","sources":["../../src/util/txe_account_data_provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAiB,MAAM,iBAAiB,CAAC;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,qBAAa,sBAAsB;;gBAGrB,KAAK,EAAE,iBAAiB;IAI9B,UAAU,CAAC,GAAG,EAAE,YAAY;IAQ5B,UAAU,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,eAAe;CAG3D"}
@@ -0,0 +1,17 @@
1
+ import { CompleteAddress } from '@aztec/stdlib/contract';
2
+ export class TXEAccountDataProvider {
3
+ #accounts;
4
+ constructor(store){
5
+ this.#accounts = store.openMap('accounts');
6
+ }
7
+ async getAccount(key) {
8
+ const completeAddress = await this.#accounts.getAsync(key.toString());
9
+ if (!completeAddress) {
10
+ throw new Error(`Account not found: ${key.toString()}`);
11
+ }
12
+ return CompleteAddress.fromBuffer(completeAddress);
13
+ }
14
+ async setAccount(key, value) {
15
+ await this.#accounts.set(key.toString(), value.toBuffer());
16
+ }
17
+ }
@@ -0,0 +1,20 @@
1
+ import { Fr } from '@aztec/foundation/fields';
2
+ import { type ContractArtifact, FunctionSelector } from '@aztec/stdlib/abi';
3
+ import type { AztecAddress } from '@aztec/stdlib/aztec-address';
4
+ import { type ContractClassPublic, type ContractDataSource, type ContractInstanceWithAddress, type PublicFunction } from '@aztec/stdlib/contract';
5
+ import type { TXE } from '../oracle/txe_oracle.js';
6
+ export declare class TXEPublicContractDataSource implements ContractDataSource {
7
+ private txeOracle;
8
+ constructor(txeOracle: TXE);
9
+ getPublicFunction(address: AztecAddress, selector: FunctionSelector): Promise<PublicFunction | undefined>;
10
+ getBlockNumber(): Promise<number>;
11
+ getContractClass(id: Fr): Promise<ContractClassPublic | undefined>;
12
+ getBytecodeCommitment(id: Fr): Promise<Fr | undefined>;
13
+ getContract(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined>;
14
+ getContractClassIds(): Promise<Fr[]>;
15
+ getContractArtifact(address: AztecAddress): Promise<ContractArtifact | undefined>;
16
+ getContractFunctionName(address: AztecAddress, selector: FunctionSelector): Promise<string | undefined>;
17
+ registerContractFunctionSignatures(_address: AztecAddress, _signatures: []): Promise<void>;
18
+ addContractClass(_contractClass: ContractClassPublic): Promise<void>;
19
+ }
20
+ //# sourceMappingURL=txe_public_contract_data_source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"txe_public_contract_data_source.d.ts","sourceRoot":"","sources":["../../src/util/txe_public_contract_data_source.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,EAAE,EAAE,MAAM,0BAA0B,CAAC;AAE9C,OAAO,EAAE,KAAK,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC5E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,2BAA2B,EAChC,KAAK,cAAc,EAEpB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,yBAAyB,CAAC;AAEnD,qBAAa,2BAA4B,YAAW,kBAAkB;IACxD,OAAO,CAAC,SAAS;gBAAT,SAAS,EAAE,GAAG;IAE5B,iBAAiB,CAAC,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,gBAAgB,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IAQ/G,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;IAI3B,gBAAgB,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;IA0BlE,qBAAqB,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,EAAE,GAAG,SAAS,CAAC;IAKtD,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,2BAA2B,GAAG,SAAS,CAAC;IAK1F,mBAAmB,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC;IAI9B,mBAAmB,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC;IAKjF,uBAAuB,CAAC,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAgB7G,kCAAkC,CAAC,QAAQ,EAAE,YAAY,EAAE,WAAW,EAAE,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAK1F,gBAAgB,CAAC,cAAc,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;CAIrE"}
@@ -0,0 +1,88 @@
1
+ import { PUBLIC_DISPATCH_SELECTOR } from '@aztec/constants';
2
+ import { Fr } from '@aztec/foundation/fields';
3
+ import { PrivateFunctionsTree } from '@aztec/pxe/server';
4
+ import { FunctionSelector } from '@aztec/stdlib/abi';
5
+ import { computePublicBytecodeCommitment } from '@aztec/stdlib/contract';
6
+ export class TXEPublicContractDataSource {
7
+ txeOracle;
8
+ constructor(txeOracle){
9
+ this.txeOracle = txeOracle;
10
+ }
11
+ async getPublicFunction(address, selector) {
12
+ const bytecode = await this.txeOracle.getContractDataProvider().getBytecode(address, selector);
13
+ if (!bytecode) {
14
+ return undefined;
15
+ }
16
+ return {
17
+ bytecode,
18
+ selector
19
+ };
20
+ }
21
+ getBlockNumber() {
22
+ return this.txeOracle.getBlockNumber();
23
+ }
24
+ async getContractClass(id) {
25
+ const contractClass = await this.txeOracle.getContractDataProvider().getContractClass(id);
26
+ const artifact = await this.txeOracle.getContractDataProvider().getContractArtifact(id);
27
+ const tree = await PrivateFunctionsTree.create(artifact);
28
+ const privateFunctionsRoot = await tree.getFunctionTreeRoot();
29
+ const publicFunctions = [];
30
+ if (contractClass.packedBytecode.length > 0) {
31
+ publicFunctions.push({
32
+ selector: FunctionSelector.fromField(new Fr(PUBLIC_DISPATCH_SELECTOR)),
33
+ bytecode: contractClass.packedBytecode
34
+ });
35
+ }
36
+ return {
37
+ id,
38
+ artifactHash: contractClass.artifactHash,
39
+ packedBytecode: contractClass.packedBytecode,
40
+ publicFunctions: publicFunctions,
41
+ privateFunctionsRoot: new Fr(privateFunctionsRoot.root),
42
+ version: contractClass.version,
43
+ privateFunctions: [],
44
+ unconstrainedFunctions: []
45
+ };
46
+ }
47
+ async getBytecodeCommitment(id) {
48
+ const contractClass = await this.txeOracle.getContractDataProvider().getContractClass(id);
49
+ return computePublicBytecodeCommitment(contractClass.packedBytecode);
50
+ }
51
+ async getContract(address) {
52
+ const instance = await this.txeOracle.getContractDataProvider().getContractInstance(address);
53
+ return {
54
+ ...instance,
55
+ address
56
+ };
57
+ }
58
+ getContractClassIds() {
59
+ throw new Error('Method not implemented.');
60
+ }
61
+ async getContractArtifact(address) {
62
+ const instance = await this.txeOracle.getContractDataProvider().getContractInstance(address);
63
+ return this.txeOracle.getContractDataProvider().getContractArtifact(instance.currentContractClassId);
64
+ }
65
+ async getContractFunctionName(address, selector) {
66
+ const artifact = await this.getContractArtifact(address);
67
+ if (!artifact) {
68
+ return undefined;
69
+ }
70
+ const functionSelectorsAndNames = await Promise.all(artifact.functions.map(async (f)=>({
71
+ name: f.name,
72
+ selector: await FunctionSelector.fromNameAndParameters({
73
+ name: f.name,
74
+ parameters: f.parameters
75
+ })
76
+ })));
77
+ const func = functionSelectorsAndNames.find((f)=>f.selector.equals(selector));
78
+ return Promise.resolve(func?.name);
79
+ }
80
+ registerContractFunctionSignatures(_address, _signatures) {
81
+ return Promise.resolve();
82
+ }
83
+ // TODO(#10007): Remove this method.
84
+ addContractClass(_contractClass) {
85
+ // We don't really need to do anything for the txe here
86
+ return Promise.resolve();
87
+ }
88
+ }
@@ -0,0 +1,14 @@
1
+ import { Fr } from '@aztec/foundation/fields';
2
+ import { WorldStateDB } from '@aztec/simulator/server';
3
+ import type { AztecAddress } from '@aztec/stdlib/aztec-address';
4
+ import type { ContractDataSource } from '@aztec/stdlib/contract';
5
+ import type { MerkleTreeWriteOperations } from '@aztec/stdlib/interfaces/server';
6
+ import type { TXE } from '../oracle/txe_oracle.js';
7
+ export declare class TXEWorldStateDB extends WorldStateDB {
8
+ private merkleDb;
9
+ private txe;
10
+ constructor(merkleDb: MerkleTreeWriteOperations, dataSource: ContractDataSource, txe: TXE);
11
+ storageRead(contract: AztecAddress, slot: Fr): Promise<Fr>;
12
+ storageWrite(contract: AztecAddress, slot: Fr, newValue: Fr): Promise<void>;
13
+ }
14
+ //# sourceMappingURL=txe_world_state_db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"txe_world_state_db.d.ts","sourceRoot":"","sources":["../../src/util/txe_world_state_db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,0BAA0B,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAEvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAEjE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,iCAAiC,CAAC;AAGjF,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,yBAAyB,CAAC;AAEnD,qBAAa,eAAgB,SAAQ,YAAY;IACnC,OAAO,CAAC,QAAQ;IAA6D,OAAO,CAAC,GAAG;gBAAhF,QAAQ,EAAE,yBAAyB,EAAE,UAAU,EAAE,kBAAkB,EAAU,GAAG,EAAE,GAAG;IAI1F,WAAW,CAAC,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,EAAE,GAAG,OAAO,CAAC,EAAE,CAAC;IAgB1D,YAAY,CAAC,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAK3F"}
@@ -0,0 +1,27 @@
1
+ import { Fr } from '@aztec/foundation/fields';
2
+ import { WorldStateDB } from '@aztec/simulator/server';
3
+ import { PublicDataWrite } from '@aztec/stdlib/avm';
4
+ import { computePublicDataTreeLeafSlot } from '@aztec/stdlib/hash';
5
+ import { MerkleTreeId } from '@aztec/stdlib/trees';
6
+ export class TXEWorldStateDB extends WorldStateDB {
7
+ merkleDb;
8
+ txe;
9
+ constructor(merkleDb, dataSource, txe){
10
+ super(merkleDb, dataSource), this.merkleDb = merkleDb, this.txe = txe;
11
+ }
12
+ async storageRead(contract, slot) {
13
+ const leafSlot = (await computePublicDataTreeLeafSlot(contract, slot)).toBigInt();
14
+ const lowLeafResult = await this.merkleDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot);
15
+ let value = Fr.ZERO;
16
+ if (lowLeafResult && lowLeafResult.alreadyPresent) {
17
+ const preimage = await this.merkleDb.getLeafPreimage(MerkleTreeId.PUBLIC_DATA_TREE, lowLeafResult.index);
18
+ value = preimage.value;
19
+ }
20
+ return value;
21
+ }
22
+ async storageWrite(contract, slot, newValue) {
23
+ await this.txe.addPublicDataWrites([
24
+ new PublicDataWrite(await computePublicDataTreeLeafSlot(contract, slot), newValue)
25
+ ]);
26
+ }
27
+ }
package/package.json ADDED
@@ -0,0 +1,93 @@
1
+ {
2
+ "name": "@aztec/txe",
3
+ "version": "0.0.0-test.0",
4
+ "type": "module",
5
+ "exports": "./dest/index.js",
6
+ "bin": "./dest/bin/index.js",
7
+ "typedocOptions": {
8
+ "entryPoints": [
9
+ "./src/index.ts"
10
+ ],
11
+ "name": "TXE",
12
+ "tsconfig": "./tsconfig.json"
13
+ },
14
+ "scripts": {
15
+ "build": "yarn clean && tsc -b",
16
+ "build:dev": "tsc -b --watch",
17
+ "clean": "rm -rf ./dest .tsbuildinfo",
18
+ "formatting": "run -T prettier --check ./src && run -T eslint ./src",
19
+ "formatting:fix": "run -T eslint --fix ./src && run -T prettier -w ./src",
20
+ "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests --maxWorkers=${JEST_MAX_WORKERS:-8}",
21
+ "dev": "LOG_LEVEL=debug node ./dest/bin/index.js",
22
+ "start": "node --no-warnings ./dest/bin/index.js"
23
+ },
24
+ "inherits": [
25
+ "../package.common.json"
26
+ ],
27
+ "jest": {
28
+ "moduleNameMapper": {
29
+ "^(\\.{1,2}/.*)\\.[cm]?js$": "$1"
30
+ },
31
+ "testRegex": "./src/.*\\.test\\.(js|mjs|ts)$",
32
+ "rootDir": "./src",
33
+ "workerThreads": true,
34
+ "transform": {
35
+ "^.+\\.tsx?$": [
36
+ "@swc/jest",
37
+ {
38
+ "jsc": {
39
+ "parser": {
40
+ "syntax": "typescript",
41
+ "decorators": true
42
+ },
43
+ "transform": {
44
+ "decoratorVersion": "2022-03"
45
+ }
46
+ }
47
+ }
48
+ ]
49
+ },
50
+ "extensionsToTreatAsEsm": [
51
+ ".ts"
52
+ ],
53
+ "reporters": [
54
+ "default"
55
+ ],
56
+ "testTimeout": 120000,
57
+ "setupFiles": [
58
+ "../../foundation/src/jest/setup.mjs"
59
+ ]
60
+ },
61
+ "dependencies": {
62
+ "@aztec/accounts": "0.0.0-test.0",
63
+ "@aztec/aztec.js": "0.0.0-test.0",
64
+ "@aztec/constants": "0.0.0-test.0",
65
+ "@aztec/foundation": "0.0.0-test.0",
66
+ "@aztec/key-store": "0.0.0-test.0",
67
+ "@aztec/kv-store": "0.0.0-test.0",
68
+ "@aztec/protocol-contracts": "0.0.0-test.0",
69
+ "@aztec/pxe": "0.0.0-test.0",
70
+ "@aztec/simulator": "0.0.0-test.0",
71
+ "@aztec/stdlib": "0.0.0-test.0",
72
+ "@aztec/world-state": "0.0.0-test.0",
73
+ "zod": "^3.23.8"
74
+ },
75
+ "devDependencies": {
76
+ "@jest/globals": "^29.5.0",
77
+ "@types/jest": "^29.5.0",
78
+ "@types/node": "^18.7.23",
79
+ "jest": "^29.5.0",
80
+ "jest-mock-extended": "^3.0.3",
81
+ "ts-node": "^10.9.1",
82
+ "typescript": "^5.0.4"
83
+ },
84
+ "files": [
85
+ "dest",
86
+ "src",
87
+ "!*.test.*"
88
+ ],
89
+ "types": "./dest/index.d.ts",
90
+ "engines": {
91
+ "node": ">=18"
92
+ }
93
+ }
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env -S node --no-warnings
2
+ import { createLogger } from '@aztec/aztec.js';
3
+ import { startHttpRpcServer } from '@aztec/foundation/json-rpc/server';
4
+
5
+ import { createTXERpcServer } from '../index.js';
6
+
7
+ /**
8
+ * Create and start a new TXE HTTP Server
9
+ */
10
+ async function main() {
11
+ const { TXE_PORT = 8080 } = process.env;
12
+
13
+ process.on('SIGTERM', () => {
14
+ logger.info('Received SIGTERM.');
15
+ process.exit(0);
16
+ });
17
+
18
+ process.on('SIGINT', () => {
19
+ logger.info('Received SIGTERM.');
20
+ process.exit(0);
21
+ });
22
+
23
+ const logger = createLogger('txe:service');
24
+ logger.info(`Setting up TXE...`);
25
+
26
+ const txeServer = createTXERpcServer(logger);
27
+ const { port } = await startHttpRpcServer(txeServer, {
28
+ port: TXE_PORT,
29
+ timeoutMs: 1e3 * 60 * 5,
30
+ });
31
+
32
+ logger.info(`TXE listening on port ${port}`);
33
+ }
34
+
35
+ main().catch(err => {
36
+ // eslint-disable-next-line no-console
37
+ console.error(err);
38
+ process.exit(1);
39
+ });
package/src/index.ts ADDED
@@ -0,0 +1,213 @@
1
+ import { SchnorrAccountContractArtifact } from '@aztec/accounts/schnorr';
2
+ import {
3
+ AztecAddress,
4
+ type ContractArtifact,
5
+ type ContractInstanceWithAddress,
6
+ Fr,
7
+ PublicKeys,
8
+ deriveKeys,
9
+ getContractInstanceFromDeployParams,
10
+ loadContractArtifact,
11
+ } from '@aztec/aztec.js';
12
+ import { createSafeJsonRpcServer } from '@aztec/foundation/json-rpc/server';
13
+ import type { Logger } from '@aztec/foundation/log';
14
+ import { type ProtocolContract, protocolContractNames } from '@aztec/protocol-contracts';
15
+ import { BundledProtocolContractsProvider } from '@aztec/protocol-contracts/providers/bundle';
16
+ import type { ApiSchemaFor, ZodFor } from '@aztec/stdlib/schemas';
17
+
18
+ import { readFile, readdir } from 'fs/promises';
19
+ import { join } from 'path';
20
+ import { z } from 'zod';
21
+
22
+ import { TXEService } from './txe_service/txe_service.js';
23
+ import {
24
+ type ForeignCallArgs,
25
+ ForeignCallArgsSchema,
26
+ type ForeignCallArray,
27
+ type ForeignCallResult,
28
+ ForeignCallResultSchema,
29
+ type ForeignCallSingle,
30
+ fromArray,
31
+ fromSingle,
32
+ toForeignCallResult,
33
+ toSingle,
34
+ } from './util/encoding.js';
35
+
36
+ const TXESessions = new Map<number, TXEService>();
37
+
38
+ const TXEArtifactsCache = new Map<string, { artifact: ContractArtifact; instance: ContractInstanceWithAddress }>();
39
+
40
+ type MethodNames<T> = {
41
+ [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never;
42
+ }[keyof T];
43
+
44
+ type TXEForeignCallInput = {
45
+ session_id: number;
46
+ function: MethodNames<TXEService> | 'reset';
47
+ root_path: string;
48
+ package_name: string;
49
+ inputs: ForeignCallArgs;
50
+ };
51
+
52
+ const TXEForeignCallInputSchema = z.object({
53
+ // eslint-disable-next-line camelcase
54
+ session_id: z.number().int().nonnegative(),
55
+ function: z.string() as ZodFor<MethodNames<TXEService> | 'reset'>,
56
+ // eslint-disable-next-line camelcase
57
+ root_path: z.string(),
58
+ // eslint-disable-next-line camelcase
59
+ package_name: z.string(),
60
+ inputs: ForeignCallArgsSchema,
61
+ }) satisfies ZodFor<TXEForeignCallInput>;
62
+
63
+ class TXEDispatcher {
64
+ private protocolContracts!: ProtocolContract[];
65
+
66
+ constructor(private logger: Logger) {}
67
+
68
+ async #processDeployInputs({ inputs, root_path: rootPath, package_name: packageName }: TXEForeignCallInput) {
69
+ const [pathStr, contractName, initializer] = inputs.slice(0, 3).map(input =>
70
+ fromArray(input as ForeignCallArray)
71
+ .map(char => String.fromCharCode(char.toNumber()))
72
+ .join(''),
73
+ );
74
+
75
+ const decodedArgs = fromArray(inputs[4] as ForeignCallArray);
76
+ const secret = fromSingle(inputs[5] as ForeignCallSingle);
77
+ const publicKeys = secret.equals(Fr.ZERO) ? PublicKeys.default() : (await deriveKeys(secret)).publicKeys;
78
+ const publicKeysHash = await publicKeys.hash();
79
+
80
+ const cacheKey = `${pathStr}-${contractName}-${initializer}-${decodedArgs
81
+ .map(arg => arg.toString())
82
+ .join('-')}-${publicKeysHash.toString()}`;
83
+
84
+ let artifact;
85
+ let instance;
86
+
87
+ if (TXEArtifactsCache.has(cacheKey)) {
88
+ this.logger.debug(`Using cached artifact for ${cacheKey}`);
89
+ ({ artifact, instance } = TXEArtifactsCache.get(cacheKey)!);
90
+ } else {
91
+ let artifactPath = '';
92
+ // We're deploying the contract under test
93
+ // env.deploy_self("contractName")
94
+ if (!pathStr) {
95
+ artifactPath = join(rootPath, './target', `${packageName}-${contractName}.json`);
96
+ } else {
97
+ // We're deploying a contract that belongs in a workspace
98
+ // env.deploy("../path/to/workspace/root@packageName", "contractName")
99
+ if (pathStr.includes('@')) {
100
+ const [workspace, pkg] = pathStr.split('@');
101
+ const targetPath = join(rootPath, workspace, './target');
102
+ this.logger.debug(`Looking for compiled artifact in workspace ${targetPath}`);
103
+ artifactPath = join(targetPath, `${pkg}-${contractName}.json`);
104
+ } else {
105
+ // We're deploying a standalone contract
106
+ // env.deploy("../path/to/contract/root", "contractName")
107
+ const targetPath = join(rootPath, pathStr, './target');
108
+ this.logger.debug(`Looking for compiled artifact in ${targetPath}`);
109
+ [artifactPath] = (await readdir(targetPath)).filter(file => file.endsWith(`-${contractName}.json`));
110
+ }
111
+ }
112
+ this.logger.debug(`Loading compiled artifact ${artifactPath}`);
113
+ artifact = loadContractArtifact(JSON.parse(await readFile(artifactPath, 'utf-8')));
114
+ this.logger.debug(
115
+ `Deploy ${
116
+ artifact.name
117
+ } with initializer ${initializer}(${decodedArgs}) and public keys hash ${publicKeysHash.toString()}`,
118
+ );
119
+ instance = await getContractInstanceFromDeployParams(artifact, {
120
+ constructorArgs: decodedArgs,
121
+ skipArgsDecoding: true,
122
+ salt: Fr.ONE,
123
+ publicKeys,
124
+ constructorArtifact: initializer ? initializer : undefined,
125
+ deployer: AztecAddress.ZERO,
126
+ });
127
+ TXEArtifactsCache.set(cacheKey, { artifact, instance });
128
+ }
129
+
130
+ inputs.splice(0, 2, artifact, instance, toSingle(secret));
131
+ }
132
+
133
+ async #processAddAccountInputs({ inputs }: TXEForeignCallInput) {
134
+ const secret = fromSingle(inputs[0] as ForeignCallSingle);
135
+
136
+ const cacheKey = `SchnorrAccountContract-${secret}`;
137
+
138
+ let artifact;
139
+ let instance;
140
+
141
+ if (TXEArtifactsCache.has(cacheKey)) {
142
+ this.logger.debug(`Using cached artifact for ${cacheKey}`);
143
+ ({ artifact, instance } = TXEArtifactsCache.get(cacheKey)!);
144
+ } else {
145
+ const keys = await deriveKeys(secret);
146
+ const args = [keys.publicKeys.masterIncomingViewingPublicKey.x, keys.publicKeys.masterIncomingViewingPublicKey.y];
147
+ artifact = SchnorrAccountContractArtifact;
148
+ instance = await getContractInstanceFromDeployParams(artifact, {
149
+ constructorArgs: args,
150
+ skipArgsDecoding: true,
151
+ salt: Fr.ONE,
152
+ publicKeys: keys.publicKeys,
153
+ constructorArtifact: 'constructor',
154
+ deployer: AztecAddress.ZERO,
155
+ });
156
+ TXEArtifactsCache.set(cacheKey, { artifact, instance });
157
+ }
158
+
159
+ inputs.splice(0, 0, artifact, instance);
160
+ }
161
+
162
+ // eslint-disable-next-line camelcase
163
+ async resolve_foreign_call(callData: TXEForeignCallInput): Promise<ForeignCallResult> {
164
+ const { session_id: sessionId, function: functionName, inputs } = callData;
165
+ this.logger.debug(`Calling ${functionName} on session ${sessionId}`);
166
+
167
+ if (!TXESessions.has(sessionId) && functionName != 'reset') {
168
+ this.logger.debug(`Creating new session ${sessionId}`);
169
+ if (!this.protocolContracts) {
170
+ this.protocolContracts = await Promise.all(
171
+ protocolContractNames.map(name => new BundledProtocolContractsProvider().getProtocolContractArtifact(name)),
172
+ );
173
+ }
174
+ TXESessions.set(sessionId, await TXEService.init(this.logger, this.protocolContracts));
175
+ }
176
+
177
+ switch (functionName) {
178
+ case 'reset': {
179
+ TXESessions.delete(sessionId) &&
180
+ this.logger.debug(`Called reset on session ${sessionId}, yeeting it out of existence`);
181
+ return toForeignCallResult([]);
182
+ }
183
+ case 'deploy': {
184
+ await this.#processDeployInputs(callData);
185
+ break;
186
+ }
187
+ case 'addAccount': {
188
+ await this.#processAddAccountInputs(callData);
189
+ break;
190
+ }
191
+ }
192
+
193
+ const txeService = TXESessions.get(sessionId);
194
+ const response = await (txeService as any)[functionName](...inputs);
195
+ return response;
196
+ }
197
+ }
198
+
199
+ const TXEDispatcherApiSchema: ApiSchemaFor<TXEDispatcher> = {
200
+ // eslint-disable-next-line camelcase
201
+ resolve_foreign_call: z.function().args(TXEForeignCallInputSchema).returns(ForeignCallResultSchema),
202
+ };
203
+
204
+ /**
205
+ * Creates an RPC server that forwards calls to the TXE.
206
+ * @param logger - Logger to output to
207
+ * @returns A TXE RPC server.
208
+ */
209
+ export function createTXERpcServer(logger: Logger) {
210
+ return createSafeJsonRpcServer(new TXEDispatcher(logger), TXEDispatcherApiSchema, {
211
+ http200OnError: true,
212
+ });
213
+ }