@aztec/native 0.73.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.
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # Native module
2
+
3
+ A package containing all the native bindings needed to run Aztec.
@@ -0,0 +1,3 @@
1
+ export * from './native_module.js';
2
+ export { RoundtripDuration, MsgpackChannel } from './msgpack_channel.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC"}
package/dest/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from './native_module.js';
2
+ export { MsgpackChannel } from './msgpack_channel.js';
3
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYyxvQkFBb0IsQ0FBQztBQUNuQyxPQUFPLEVBQXFCLGNBQWMsRUFBRSxNQUFNLHNCQUFzQixDQUFDIn0=
@@ -0,0 +1,27 @@
1
+ /// <reference types="node" resolution-mode="require"/>
2
+ /// <reference types="node" resolution-mode="require"/>
3
+ export interface MessageReceiver {
4
+ call(msg: Buffer | Uint8Array): Promise<Buffer | Uint8Array>;
5
+ }
6
+ export type RoundtripDuration = {
7
+ encodingUs: number;
8
+ callUs: number;
9
+ decodingUs: number;
10
+ totalUs: number;
11
+ };
12
+ type MessageBody<T extends number> = {
13
+ [K in T]: object | void;
14
+ };
15
+ export declare class MsgpackChannel<M extends number = number, Req extends MessageBody<M> = any, Resp extends MessageBody<M> = any> {
16
+ private dest;
17
+ /** A long-lived msgpack encoder */
18
+ private encoder;
19
+ private msgId;
20
+ constructor(dest: MessageReceiver);
21
+ sendMessage<T extends M>(msgType: T, body: Req[T]): Promise<{
22
+ duration: RoundtripDuration;
23
+ response: Resp[T];
24
+ }>;
25
+ }
26
+ export {};
27
+ //# sourceMappingURL=msgpack_channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"msgpack_channel.d.ts","sourceRoot":"","sources":["../src/msgpack_channel.ts"],"names":[],"mappings":";;AAMA,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC,MAAM,GAAG,UAAU,CAAC,CAAC;CAC9D;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAYF,KAAK,WAAW,CAAC,CAAC,SAAS,MAAM,IAAI;KAAG,CAAC,IAAI,CAAC,GAAG,MAAM,GAAG,IAAI;CAAE,CAAC;AAEjE,qBAAa,cAAc,CACzB,CAAC,SAAS,MAAM,GAAG,MAAM,EACzB,GAAG,SAAS,WAAW,CAAC,CAAC,CAAC,GAAG,GAAG,EAChC,IAAI,SAAS,WAAW,CAAC,CAAC,CAAC,GAAG,GAAG;IAYd,OAAO,CAAC,IAAI;IAV/B,mCAAmC;IACnC,OAAO,CAAC,OAAO,CAKZ;IAEH,OAAO,CAAC,KAAK,CAAK;gBAES,IAAI,EAAE,eAAe;IAEnC,WAAW,CAAC,CAAC,SAAS,CAAC,EAClC,OAAO,EAAE,CAAC,EACV,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,GACX,OAAO,CAAC;QAAE,QAAQ,EAAE,iBAAiB,CAAC;QAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,CAAA;KAAE,CAAC;CA2D/D"}
@@ -0,0 +1,69 @@
1
+ import { Fr } from '@aztec/foundation/fields';
2
+ import { MessageHeader, TypedMessage } from '@aztec/foundation/message';
3
+ import { Encoder, addExtension } from 'msgpackr';
4
+ import { isAnyArrayBuffer } from 'util/types';
5
+ // small extension to pack an NodeJS Fr instance to a representation that the C++ code can understand
6
+ // this only works for writes. Unpacking from C++ can't create Fr instances because the data is passed
7
+ // as raw, untagged, buffers. On the NodeJS side we don't know what the buffer represents
8
+ // Adding a tag would be a solution, but it would have to be done on both sides and it's unclear where else
9
+ // C++ fr instances are sent/received/stored.
10
+ addExtension({
11
+ Class: Fr,
12
+ write: fr => fr.toBuffer(),
13
+ });
14
+ export class MsgpackChannel {
15
+ constructor(dest) {
16
+ this.dest = dest;
17
+ /** A long-lived msgpack encoder */
18
+ this.encoder = new Encoder({
19
+ // always encode JS objects as MessagePack maps
20
+ // this makes it compatible with other MessagePack decoders
21
+ useRecords: false,
22
+ int64AsType: 'bigint',
23
+ });
24
+ this.msgId = 1;
25
+ }
26
+ async sendMessage(msgType, body) {
27
+ const duration = {
28
+ callUs: 0,
29
+ totalUs: 0,
30
+ decodingUs: 0,
31
+ encodingUs: 0,
32
+ };
33
+ const start = process.hrtime.bigint();
34
+ const requestId = this.msgId++;
35
+ const request = new TypedMessage(msgType, new MessageHeader({ requestId }), body);
36
+ const encodedRequest = this.encoder.encode(request);
37
+ const encodingEnd = process.hrtime.bigint();
38
+ duration.encodingUs = Number((encodingEnd - start) / 1000n);
39
+ const encodedResponse = await this.dest.call(encodedRequest);
40
+ const callEnd = process.hrtime.bigint();
41
+ duration.callUs = Number((callEnd - encodingEnd) / 1000n);
42
+ const buf = Buffer.isBuffer(encodedResponse)
43
+ ? encodedResponse
44
+ : isAnyArrayBuffer(encodedResponse)
45
+ ? Buffer.from(encodedResponse)
46
+ : encodedResponse;
47
+ if (!Buffer.isBuffer(buf)) {
48
+ throw new TypeError('Invalid encoded response: expected Buffer or ArrayBuffer, got ' +
49
+ (encodedResponse === null ? 'null' : typeof encodedResponse));
50
+ }
51
+ const decodedResponse = this.encoder.unpack(buf);
52
+ if (!TypedMessage.isTypedMessageLike(decodedResponse)) {
53
+ throw new TypeError('Invalid response: expected TypedMessageLike, got ' +
54
+ (decodedResponse === null ? 'null' : typeof decodedResponse));
55
+ }
56
+ const response = TypedMessage.fromMessagePack(decodedResponse);
57
+ const decodingEnd = process.hrtime.bigint();
58
+ duration.decodingUs = Number((decodingEnd - callEnd) / 1000n);
59
+ if (response.header.requestId !== request.header.messageId) {
60
+ throw new Error('Response ID does not match request: ' + response.header.requestId + ' != ' + request.header.messageId);
61
+ }
62
+ if (response.msgType !== request.msgType) {
63
+ throw new Error('Invalid response message type: ' + response.msgType + ' != ' + response.msgType);
64
+ }
65
+ duration.totalUs = Number((process.hrtime.bigint() - start) / 1000n);
66
+ return { duration, response: response.value };
67
+ }
68
+ }
69
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibXNncGFja19jaGFubmVsLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL21zZ3BhY2tfY2hhbm5lbC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsRUFBRSxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDOUMsT0FBTyxFQUFFLGFBQWEsRUFBRSxZQUFZLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUV4RSxPQUFPLEVBQUUsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLFVBQVUsQ0FBQztBQUNqRCxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxZQUFZLENBQUM7QUFhOUMscUdBQXFHO0FBQ3JHLHNHQUFzRztBQUN0Ryx5RkFBeUY7QUFDekYsMkdBQTJHO0FBQzNHLDZDQUE2QztBQUM3QyxZQUFZLENBQUM7SUFDWCxLQUFLLEVBQUUsRUFBRTtJQUNULEtBQUssRUFBRSxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxRQUFRLEVBQUU7Q0FDM0IsQ0FBQyxDQUFDO0FBSUgsTUFBTSxPQUFPLGNBQWM7SUFlekIsWUFBMkIsSUFBcUI7UUFBckIsU0FBSSxHQUFKLElBQUksQ0FBaUI7UUFWaEQsbUNBQW1DO1FBQzNCLFlBQU8sR0FBRyxJQUFJLE9BQU8sQ0FBQztZQUM1QiwrQ0FBK0M7WUFDL0MsMkRBQTJEO1lBQzNELFVBQVUsRUFBRSxLQUFLO1lBQ2pCLFdBQVcsRUFBRSxRQUFRO1NBQ3RCLENBQUMsQ0FBQztRQUVLLFVBQUssR0FBRyxDQUFDLENBQUM7SUFFaUMsQ0FBQztJQUU3QyxLQUFLLENBQUMsV0FBVyxDQUN0QixPQUFVLEVBQ1YsSUFBWTtRQUVaLE1BQU0sUUFBUSxHQUFzQjtZQUNsQyxNQUFNLEVBQUUsQ0FBQztZQUNULE9BQU8sRUFBRSxDQUFDO1lBQ1YsVUFBVSxFQUFFLENBQUM7WUFDYixVQUFVLEVBQUUsQ0FBQztTQUNkLENBQUM7UUFFRixNQUFNLEtBQUssR0FBRyxPQUFPLENBQUMsTUFBTSxD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
@@ -0,0 +1,8 @@
1
+ import { type MessageReceiver } from './msgpack_channel.js';
2
+ interface NativeClassCtor {
3
+ new (...args: unknown[]): MessageReceiver;
4
+ }
5
+ export declare const NativeWorldState: NativeClassCtor;
6
+ export declare const NativeLMDBStore: NativeClassCtor;
7
+ export {};
8
+ //# sourceMappingURL=native_module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native_module.d.ts","sourceRoot":"","sources":["../src/native_module.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE5D,UAAU,eAAe;IACvB,KAAK,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,eAAe,CAAC;CAC3C;AAID,eAAO,MAAM,gBAAgB,EAAE,eAAyC,CAAC;AACzE,eAAO,MAAM,eAAe,EAAE,eAAwC,CAAC"}
@@ -0,0 +1,5 @@
1
+ import bindings from 'bindings';
2
+ const nativeModule = bindings('nodejs_module');
3
+ export const NativeWorldState = nativeModule.WorldState;
4
+ export const NativeLMDBStore = nativeModule.LMDBStore;
5
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmF0aXZlX21vZHVsZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9uYXRpdmVfbW9kdWxlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sUUFBUSxNQUFNLFVBQVUsQ0FBQztBQVFoQyxNQUFNLFlBQVksR0FBb0MsUUFBUSxDQUFDLGVBQWUsQ0FBQyxDQUFDO0FBRWhGLE1BQU0sQ0FBQyxNQUFNLGdCQUFnQixHQUFvQixZQUFZLENBQUMsVUFBVSxDQUFDO0FBQ3pFLE1BQU0sQ0FBQyxNQUFNLGVBQWUsR0FBb0IsWUFBWSxDQUFDLFNBQVMsQ0FBQyJ9
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@aztec/native",
3
+ "version": "0.73.0",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": "./dest/index.js"
7
+ },
8
+ "scripts": {
9
+ "build": "yarn clean && yarn generate && tsc -b",
10
+ "build:dev": "tsc -b --watch",
11
+ "build:cpp": "PROJECT=$(pwd); cd $(git rev-parse --show-toplevel)/barretenberg/cpp; cmake --preset ${PRESET:-clang16-pic} && cmake --build --preset ${PRESET:-clang16-pic} --target nodejs_module && cd $PROJECT && yarn generate",
12
+ "clean:cpp": "rm -rf $(git rev-parse --show-toplevel)/barretenberg/cpp/build-pic",
13
+ "clean": "rm -rf ./dest .tsbuildinfo",
14
+ "formatting": "run -T prettier --check ./src && run -T eslint ./src",
15
+ "formatting:fix": "run -T eslint --fix ./src && run -T prettier -w ./src",
16
+ "test": "HARDWARE_CONCURRENCY=${HARDWARE_CONCURRENCY:-16} RAYON_NUM_THREADS=${RAYON_NUM_THREADS:-4} NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests --maxWorkers=${JEST_MAX_WORKERS:-8}",
17
+ "generate": "mkdir -p build && cp -v $(git rev-parse --show-toplevel)/barretenberg/cpp/build-pic/lib/nodejs_module.node build"
18
+ },
19
+ "inherits": [
20
+ "../package.common.json",
21
+ "./package.local.json"
22
+ ],
23
+ "dependencies": {
24
+ "@aztec/foundation": "0.73.0",
25
+ "bindings": "^1.5.0",
26
+ "msgpackr": "^1.11.2"
27
+ },
28
+ "devDependencies": {
29
+ "@jest/globals": "^29.5.0",
30
+ "@types/bindings": "^1.5.5",
31
+ "@types/jest": "^29.5.0",
32
+ "@types/node": "^18.7.23",
33
+ "jest": "^29.5.0",
34
+ "ts-node": "^10.9.1",
35
+ "typescript": "^5.0.4"
36
+ },
37
+ "files": [
38
+ "dest",
39
+ "src",
40
+ "!*.test.*"
41
+ ],
42
+ "engines": {
43
+ "node": ">=18"
44
+ },
45
+ "jest": {
46
+ "extensionsToTreatAsEsm": [
47
+ ".ts"
48
+ ],
49
+ "transform": {
50
+ "^.+\\.tsx?$": [
51
+ "@swc/jest",
52
+ {
53
+ "jsc": {
54
+ "parser": {
55
+ "syntax": "typescript",
56
+ "decorators": true
57
+ },
58
+ "transform": {
59
+ "decoratorVersion": "2022-03"
60
+ }
61
+ }
62
+ }
63
+ ]
64
+ },
65
+ "moduleNameMapper": {
66
+ "^(\\.{1,2}/.*)\\.[cm]?js$": "$1"
67
+ },
68
+ "reporters": [
69
+ "default"
70
+ ],
71
+ "testRegex": "./src/.*\\.test\\.(js|mjs|ts)$",
72
+ "rootDir": "./src",
73
+ "testTimeout": 30000,
74
+ "setupFiles": [
75
+ "../../foundation/src/jest/setup.mjs"
76
+ ]
77
+ }
78
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './native_module.js';
2
+ export { RoundtripDuration, MsgpackChannel } from './msgpack_channel.js';
@@ -0,0 +1,109 @@
1
+ import { Fr } from '@aztec/foundation/fields';
2
+ import { MessageHeader, TypedMessage } from '@aztec/foundation/message';
3
+
4
+ import { Encoder, addExtension } from 'msgpackr';
5
+ import { isAnyArrayBuffer } from 'util/types';
6
+
7
+ export interface MessageReceiver {
8
+ call(msg: Buffer | Uint8Array): Promise<Buffer | Uint8Array>;
9
+ }
10
+
11
+ export type RoundtripDuration = {
12
+ encodingUs: number;
13
+ callUs: number;
14
+ decodingUs: number;
15
+ totalUs: number;
16
+ };
17
+
18
+ // small extension to pack an NodeJS Fr instance to a representation that the C++ code can understand
19
+ // this only works for writes. Unpacking from C++ can't create Fr instances because the data is passed
20
+ // as raw, untagged, buffers. On the NodeJS side we don't know what the buffer represents
21
+ // Adding a tag would be a solution, but it would have to be done on both sides and it's unclear where else
22
+ // C++ fr instances are sent/received/stored.
23
+ addExtension({
24
+ Class: Fr,
25
+ write: fr => fr.toBuffer(),
26
+ });
27
+
28
+ type MessageBody<T extends number> = { [K in T]: object | void };
29
+
30
+ export class MsgpackChannel<
31
+ M extends number = number,
32
+ Req extends MessageBody<M> = any,
33
+ Resp extends MessageBody<M> = any,
34
+ > {
35
+ /** A long-lived msgpack encoder */
36
+ private encoder = new Encoder({
37
+ // always encode JS objects as MessagePack maps
38
+ // this makes it compatible with other MessagePack decoders
39
+ useRecords: false,
40
+ int64AsType: 'bigint',
41
+ });
42
+
43
+ private msgId = 1;
44
+
45
+ public constructor(private dest: MessageReceiver) {}
46
+
47
+ public async sendMessage<T extends M>(
48
+ msgType: T,
49
+ body: Req[T],
50
+ ): Promise<{ duration: RoundtripDuration; response: Resp[T] }> {
51
+ const duration: RoundtripDuration = {
52
+ callUs: 0,
53
+ totalUs: 0,
54
+ decodingUs: 0,
55
+ encodingUs: 0,
56
+ };
57
+
58
+ const start = process.hrtime.bigint();
59
+ const requestId = this.msgId++;
60
+
61
+ const request = new TypedMessage(msgType, new MessageHeader({ requestId }), body);
62
+ const encodedRequest = this.encoder.encode(request);
63
+ const encodingEnd = process.hrtime.bigint();
64
+ duration.encodingUs = Number((encodingEnd - start) / 1000n);
65
+
66
+ const encodedResponse = await this.dest.call(encodedRequest);
67
+ const callEnd = process.hrtime.bigint();
68
+ duration.callUs = Number((callEnd - encodingEnd) / 1000n);
69
+
70
+ const buf = Buffer.isBuffer(encodedResponse)
71
+ ? encodedResponse
72
+ : isAnyArrayBuffer(encodedResponse)
73
+ ? Buffer.from(encodedResponse)
74
+ : encodedResponse;
75
+
76
+ if (!Buffer.isBuffer(buf)) {
77
+ throw new TypeError(
78
+ 'Invalid encoded response: expected Buffer or ArrayBuffer, got ' +
79
+ (encodedResponse === null ? 'null' : typeof encodedResponse),
80
+ );
81
+ }
82
+
83
+ const decodedResponse = this.encoder.unpack(buf);
84
+ if (!TypedMessage.isTypedMessageLike(decodedResponse)) {
85
+ throw new TypeError(
86
+ 'Invalid response: expected TypedMessageLike, got ' +
87
+ (decodedResponse === null ? 'null' : typeof decodedResponse),
88
+ );
89
+ }
90
+
91
+ const response = TypedMessage.fromMessagePack<T, Resp[T]>(decodedResponse);
92
+ const decodingEnd = process.hrtime.bigint();
93
+ duration.decodingUs = Number((decodingEnd - callEnd) / 1000n);
94
+
95
+ if (response.header.requestId !== request.header.messageId) {
96
+ throw new Error(
97
+ 'Response ID does not match request: ' + response.header.requestId + ' != ' + request.header.messageId,
98
+ );
99
+ }
100
+
101
+ if (response.msgType !== request.msgType) {
102
+ throw new Error('Invalid response message type: ' + response.msgType + ' != ' + response.msgType);
103
+ }
104
+
105
+ duration.totalUs = Number((process.hrtime.bigint() - start) / 1000n);
106
+
107
+ return { duration, response: response.value };
108
+ }
109
+ }
@@ -0,0 +1,12 @@
1
+ import bindings from 'bindings';
2
+
3
+ import { type MessageReceiver } from './msgpack_channel.js';
4
+
5
+ interface NativeClassCtor {
6
+ new (...args: unknown[]): MessageReceiver;
7
+ }
8
+
9
+ const nativeModule: Record<string, NativeClassCtor> = bindings('nodejs_module');
10
+
11
+ export const NativeWorldState: NativeClassCtor = nativeModule.WorldState;
12
+ export const NativeLMDBStore: NativeClassCtor = nativeModule.LMDBStore;