@microsoft/ccf-app 2.0.0-dev4 → 2.0.0-dev8

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/consensus.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @inheritDoc CCFConsensus.getLastCommittedTxId;
3
+ */
4
+ export declare const getLastCommittedTxId: () => import("./global.js").TransactionId;
5
+ /**
6
+ * @inheritDoc CCFConsensus.getStatusForTxId;
7
+ */
8
+ export declare const getStatusForTxId: (view: number, seqno: number) => import("./global.js").TransactionStatus;
9
+ /**
10
+ * @inheritDoc CCFConsensus.getViewForSeqno;
11
+ */
12
+ export declare const getViewForSeqno: (seqno: number) => number | null;
13
+ export { TransactionStatus } from "./global";
package/consensus.js ADDED
@@ -0,0 +1,21 @@
1
+ // Copyright (c) Microsoft Corporation. All rights reserved.
2
+ // Licensed under the Apache 2.0 License.
3
+ /**
4
+ * The `consensus` module provides access to consensus information
5
+ * as observed by the local node.
6
+ *
7
+ * @module
8
+ */
9
+ import { ccf } from "./global.js";
10
+ /**
11
+ * @inheritDoc CCFConsensus.getLastCommittedTxId;
12
+ */
13
+ export const getLastCommittedTxId = ccf.consensus.getLastCommittedTxId.bind(ccf.consensus);
14
+ /**
15
+ * @inheritDoc CCFConsensus.getStatusForTxId;
16
+ */
17
+ export const getStatusForTxId = ccf.consensus.getStatusForTxId.bind(ccf.consensus);
18
+ /**
19
+ * @inheritDoc CCFConsensus.getViewForSeqno;
20
+ */
21
+ export const getViewForSeqno = ccf.consensus.getViewForSeqno.bind(ccf.consensus);
package/converters.js CHANGED
@@ -97,7 +97,7 @@ class Int32Converter {
97
97
  class Uint32Converter {
98
98
  encode(val) {
99
99
  if (val < 0 || val > 4294967295) {
100
- throw new RangeError("value is not within int32 range");
100
+ throw new RangeError("value is not within uint32 range");
101
101
  }
102
102
  const buf = new ArrayBuffer(4);
103
103
  new DataView(buf).setUint32(0, val, true);
package/crypto.d.ts CHANGED
@@ -11,7 +11,7 @@ export declare const generateRsaKeyPair: (size: number, exponent?: number | unde
11
11
  */
12
12
  export declare const wrapKey: (key: ArrayBuffer, wrappingKey: ArrayBuffer, wrapAlgo: import("./global.js").WrapAlgoParams) => ArrayBuffer;
13
13
  /**
14
- * @inheritDoc CCF.crypto.verifySignature
14
+ * @inheritDoc CCFCrypto.verifySignature
15
15
  */
16
16
  export declare const verifySignature: (algorithm: import("./global.js").SigningAlgorithm, key: string, signature: ArrayBuffer, data: ArrayBuffer) => boolean;
17
17
  /**
package/crypto.js CHANGED
@@ -27,7 +27,7 @@ export const generateRsaKeyPair = ccf.generateRsaKeyPair;
27
27
  */
28
28
  export const wrapKey = ccf.wrapKey;
29
29
  /**
30
- * @inheritDoc CCF.crypto.verifySignature
30
+ * @inheritDoc CCFCrypto.verifySignature
31
31
  */
32
32
  export const verifySignature = ccf.crypto.verifySignature;
33
33
  /**
package/endpoints.d.ts CHANGED
@@ -46,6 +46,10 @@ export interface Request<T extends JsonCompatible<T> = any> {
46
46
  * The query string of the requested URL.
47
47
  */
48
48
  query: string;
49
+ /**
50
+ * The request path of the requested URL.
51
+ */
52
+ path: string;
49
53
  /**
50
54
  * An object to access the request body in various ways.
51
55
  */
@@ -192,4 +196,8 @@ export declare type EndpointFn<A extends JsonCompatible<A> = any, B extends Resp
192
196
  * @inheritDoc CCF.rpc.setApplyWrites
193
197
  */
194
198
  export declare const setApplyWrites: (force: boolean) => void;
199
+ /**
200
+ * @inheritDoc CCF.rpc.setClaimsDigest
201
+ */
202
+ export declare const setClaimsDigest: (digest: ArrayBuffer) => void;
195
203
  export {};
package/endpoints.js CHANGED
@@ -11,3 +11,7 @@ import { ccf } from "./global.js";
11
11
  * @inheritDoc CCF.rpc.setApplyWrites
12
12
  */
13
13
  export const setApplyWrites = ccf.rpc.setApplyWrites.bind(ccf.rpc);
14
+ /**
15
+ * @inheritDoc CCF.rpc.setClaimsDigest
16
+ */
17
+ export const setClaimsDigest = ccf.rpc.setClaimsDigest.bind(ccf.rpc);
package/global.d.ts CHANGED
@@ -25,6 +25,7 @@ export declare type JsonCompatible<T> = any;
25
25
  export interface KvMap {
26
26
  has(key: ArrayBuffer): boolean;
27
27
  get(key: ArrayBuffer): ArrayBuffer | undefined;
28
+ getVersionOfPreviousWrite(key: ArrayBuffer): number | undefined;
28
29
  set(key: ArrayBuffer, value: ArrayBuffer): KvMap;
29
30
  delete(key: ArrayBuffer): boolean;
30
31
  clear(): void;
@@ -57,9 +58,9 @@ export interface Receipt {
57
58
  */
58
59
  signature: string;
59
60
  /**
60
- * Hex-encoded Merkle tree root hash.
61
+ * Certificate of the node that signed the Merkle tree root hash.
61
62
  */
62
- root: string;
63
+ cert: string;
63
64
  /**
64
65
  * Merkle tree inclusion proof as an array of ``ProofElement`` objects.
65
66
  */
@@ -78,14 +79,25 @@ export interface Receipt {
78
79
  */
79
80
  export interface HistoricalState {
80
81
  /**
81
- * The ID of the transaction.
82
+ * The ID of the transaction, formatted as '<view>.<seqno>' string.
82
83
  */
83
84
  transactionId: string;
84
85
  /**
85
86
  * The receipt for the historic transaction.
86
87
  */
87
88
  receipt: Receipt;
89
+ /**
90
+ * An object that provides access to the maps of the Key-Value Store
91
+ * associated with the historic transaction.
92
+ * Fields are map names and values are {@linkcode KvMap} objects.
93
+ */
94
+ kv: KvMaps;
95
+ }
96
+ export interface TransactionId {
97
+ view: number;
98
+ seqno: number;
88
99
  }
100
+ export declare type TransactionStatus = "Committed" | "Invalid" | "Pending" | "Unknown";
89
101
  /**
90
102
  * [RSA-OAEP](https://datatracker.ietf.org/doc/html/rfc8017)
91
103
  * key wrapping with SHA-256 as digest function.
@@ -163,6 +175,100 @@ export interface EcdsaParams {
163
175
  }
164
176
  export declare type SigningAlgorithm = RsaPkcsParams | EcdsaParams;
165
177
  export declare type DigestAlgorithm = "SHA-256";
178
+ export interface CCFCrypto {
179
+ /**
180
+ * Returns whether digital signature is valid.
181
+ *
182
+ * @param algorithm Signing algorithm and parameters
183
+ * @param key A PEM-encoded public key or X.509 certificate
184
+ * @param signature Signature to verify
185
+ * @param data Data that was signed
186
+ * @throws Will throw an error if the key is not compatible with the
187
+ * signing algorithm or if an unknown algorithm is used.
188
+ */
189
+ verifySignature(algorithm: SigningAlgorithm, key: string, signature: ArrayBuffer, data: ArrayBuffer): boolean;
190
+ }
191
+ export interface CCFRpc {
192
+ /**
193
+ * Set whether KV writes should be applied even if the response status is not 2xx.
194
+ * The default is `false`.
195
+ */
196
+ setApplyWrites(force: boolean): void;
197
+ /**
198
+ * Set a claims digest to be associated with the transaction if it succeeds. This
199
+ * digest can later be accessed from the receipt, and expanded into a full claim.
200
+ *
201
+ * The `digest` argument must be a sha-256 ArrayBuffer, eg. produced by {@link CCF.digest}
202
+ */
203
+ setClaimsDigest(digest: ArrayBuffer): void;
204
+ }
205
+ export interface CCFConsensus {
206
+ /**
207
+ * Get the ID of latest transaction known to be committed.
208
+ */
209
+ getLastCommittedTxId(): TransactionId;
210
+ /**
211
+ * Get the status of a transaction by ID, provided as a view+seqno pair.
212
+ *
213
+ * Note that this value is the node's local understanding of the status
214
+ * of that transaction in the network at call time. For a given TxID, the
215
+ * initial status is always UNKNOWN, and eventually becomes COMMITTED or
216
+ * INVALID. See the documentation section titled "Verifying Transactions"
217
+ * for more detail.
218
+ *
219
+ * UNKNOWN [Initial status]
220
+ * v ^
221
+ * PENDING
222
+ * v v
223
+ * COMMITTED INVALID [Final statuses]
224
+ *
225
+ * This status is not sampled atomically per handler: if this is called
226
+ * multiple times in a transaction handler, later calls may see more up to
227
+ * date values than earlier calls. Once a final state (COMMITTED or INVALID)
228
+ * has been reached, no further changes are possible.
229
+ *
230
+ */
231
+ getStatusForTxId(view: number, seqno: number): TransactionStatus;
232
+ /**
233
+ * Get the view associated with a given seqno, to construct a valid TxID.
234
+ * If the seqno is not known by the node, `null` is returned.
235
+ */
236
+ getViewForSeqno(seqno: number): number | null;
237
+ }
238
+ export interface CCFHistorical {
239
+ /**
240
+ * Retrieve a range of historical states containing the state written at the given
241
+ * indices.
242
+ *
243
+ * If this is not currently available, this function returns `null`
244
+ * and begins fetching the ledger entry asynchronously. This will generally
245
+ * be true for the first call for a given seqno, and it may take some time
246
+ * to completely fetch and validate. The call should be repeated later with
247
+ * the same arguments to retrieve the requested entries. This state is kept
248
+ * until it is deleted for one of the following reasons:
249
+ * - A call to {@linkcode dropCachedStates}
250
+ * - `seconds_until_expiry` seconds elapse without calling this function
251
+ * - This handle is used to request a different seqno or range
252
+ *
253
+ * The range is inclusive of both start_seqno and end_seqno. If a non-empty
254
+ * array is returned, it will always contain the full requested range; the
255
+ * array will be of length (end_seqno - start_seqno + 1).
256
+ *
257
+ * If the requested range failed to be retrieved then `null` is returned.
258
+ * This may happen if the range is not known to the node (see also
259
+ * {@linkcode CCFConsensus.getStatusForTxId | getStatusForTxId}) or not available for
260
+ * other reasons (for example, the node is missing ledger files on disk).
261
+ */
262
+ getStateRange(handle: number, startSeqno: number, endSeqno: number, secondsUntilExpiry: number): HistoricalState[] | null;
263
+ /** Drop cached states for the given handle.
264
+ *
265
+ * May be used to free up space once a historical query has been resolved,
266
+ * more aggressively than waiting for the requests to expire.
267
+ *
268
+ * Returns `true` if the handle was found and dropped, `false` otherwise.
269
+ */
270
+ dropCachedStates(handle: number): boolean;
271
+ }
166
272
  export interface CCF {
167
273
  /**
168
274
  * Convert a string into an ArrayBuffer.
@@ -221,26 +327,9 @@ export interface CCF {
221
327
  * The chain and trusted certificates are PEM-encoded bundles of X.509 certificates.
222
328
  */
223
329
  isValidX509CertChain(chain: string, trusted: string): boolean;
224
- crypto: {
225
- /**
226
- * Returns whether digital signature is valid.
227
- *
228
- * @param algorithm Signing algorithm and parameters
229
- * @param key A PEM-encoded public key or X.509 certificate
230
- * @param signature Signature to verify
231
- * @param data Data that was signed
232
- * @throws Will throw an error if the key is not compatible with the
233
- * signing algorithm or if an unknown algorithm is used.
234
- */
235
- verifySignature(algorithm: SigningAlgorithm, key: string, signature: ArrayBuffer, data: ArrayBuffer): boolean;
236
- };
237
- rpc: {
238
- /**
239
- * Set whether KV writes should be applied even if the response status is not 2xx.
240
- * The default is `false`.
241
- */
242
- setApplyWrites(force: boolean): void;
243
- };
330
+ crypto: CCFCrypto;
331
+ rpc: CCFRpc;
332
+ consensus: CCFConsensus;
244
333
  /**
245
334
  * An object that provides access to the maps of the Key-Value Store of CCF.
246
335
  * Fields are map names and values are {@linkcode KvMap} objects.
@@ -251,6 +340,7 @@ export interface CCF {
251
340
  * Only defined for endpoints with "mode" set to "historical".
252
341
  */
253
342
  historicalState?: HistoricalState;
343
+ historical: CCFHistorical;
254
344
  }
255
345
  export declare const openenclave: OpenEnclave;
256
346
  export interface EvidenceClaims {
package/historical.d.ts CHANGED
@@ -2,4 +2,12 @@
2
2
  * @inheritDoc CCF.historicalState
3
3
  */
4
4
  export declare const historicalState: import("./global.js").HistoricalState | undefined;
5
+ /**
6
+ * @inheritDoc CCFHistorical.getStateRange
7
+ */
8
+ export declare const getStateRange: (handle: number, startSeqno: number, endSeqno: number, secondsUntilExpiry: number) => import("./global.js").HistoricalState[] | null;
9
+ /**
10
+ * @inheritDoc CCFHistorical.dropCachedStates
11
+ */
12
+ export declare const dropCachedStates: (handle: number) => boolean;
5
13
  export { HistoricalState, Receipt, Proof, ProofElement } from "./global";
package/historical.js CHANGED
@@ -1,11 +1,29 @@
1
1
  // Copyright (c) Microsoft Corporation. All rights reserved.
2
2
  // Licensed under the Apache 2.0 License.
3
3
  /**
4
- * This module provides access to the historical state
5
- * in historic endpoints, corresponding to a specific transaction.
4
+ * This module provides access to historical state.
6
5
  *
7
- * Note that the Key-Value Store also reflects the historic state
8
- * and can be accessed through the {@linkcode kv} module as usual.
6
+ * There are two options to access historical state:
7
+ *
8
+ * 1. Declare the endpoint mode as `"historical"` in `app.json`
9
+ * and access historical state via `ccf.historicalState`.
10
+ *
11
+ * This option supports single transactions only and prescribes
12
+ * how the transaction ID must be passed in the request (via
13
+ * the `x-ms-ccf-transaction-id` HTTP header).
14
+ * The {@linkcode historicalState} property provides access to
15
+ * the historical state of the Key-Value store and information
16
+ * like the transaction receipt.
17
+ *
18
+ * 2. Declare the endpoint mode as `"readonly"` in `app.json` and use
19
+ * the programmatic API to request historical state.
20
+ *
21
+ * This option supports both single and multi-transaction requests.
22
+ * It also leaves the decision of how to extract the transaction ID(s)
23
+ * from the HTTP request to the app developer.
24
+ * The {@linkcode getStateRange} function of this module
25
+ * provides access to a sequential range of transactions.
26
+ * See the documentation of that function for more details.
9
27
  *
10
28
  * @module
11
29
  */
@@ -14,3 +32,11 @@ import { ccf } from "./global.js";
14
32
  * @inheritDoc CCF.historicalState
15
33
  */
16
34
  export const historicalState = ccf.historicalState;
35
+ /**
36
+ * @inheritDoc CCFHistorical.getStateRange
37
+ */
38
+ export const getStateRange = ccf.historical.getStateRange.bind(ccf.historical);
39
+ /**
40
+ * @inheritDoc CCFHistorical.dropCachedStates
41
+ */
42
+ export const dropCachedStates = ccf.historical.dropCachedStates.bind(ccf.historical);
package/index.d.ts CHANGED
@@ -13,5 +13,6 @@
13
13
  */
14
14
  export * from "./kv.js";
15
15
  export * from "./converters.js";
16
+ export * from "./consensus.js";
16
17
  export * from "./historical.js";
17
18
  export * from "./endpoints.js";
package/index.js CHANGED
@@ -15,5 +15,6 @@
15
15
  */
16
16
  export * from "./kv.js";
17
17
  export * from "./converters.js";
18
+ export * from "./consensus.js";
18
19
  export * from "./historical.js";
19
20
  export * from "./endpoints.js";
package/kv.d.ts CHANGED
@@ -20,6 +20,17 @@
20
20
  * foo.set("key-1", {"prop1": 42});
21
21
  * ```
22
22
  *
23
+ * Example of using typed access with historical state:
24
+ * ```
25
+ * import * as ccfapp from '@microsoft/ccf-app';
26
+ *
27
+ * const states = ccfapp.getStateRange(handle, begin, end, expiry);
28
+ * // ... error handling ...
29
+ * const firstKv = states[0].kv;
30
+ * const foo = ccfapp.typedKv(firstKv['foo'], ccfapp.string, ccfapp.json);
31
+ * const val = foo.get("key-1");
32
+ * ```
33
+ *
23
34
  * @module
24
35
  */
25
36
  import { KvMap } from "./global.js";
@@ -45,11 +56,12 @@ export declare class TypedKvMap<K, V> {
45
56
  *
46
57
  * See the {@linkcode converters} module for available converters.
47
58
  *
48
- * @param name The map name in the Key-Value Store.
59
+ * @param nameOrMap Either the map name in the Key-Value Store,
60
+ * or a ``KvMap`` object.
49
61
  * @param kt The converter to use for map keys.
50
62
  * @param vt The converter to use for map values.
51
63
  */
52
- export declare function typedKv<K, V>(name: string, kt: DataConverter<K>, vt: DataConverter<V>): TypedKvMap<K, V>;
64
+ export declare function typedKv<K, V>(nameOrMap: string | KvMap, kt: DataConverter<K>, vt: DataConverter<V>): TypedKvMap<K, V>;
53
65
  /**
54
66
  * @inheritDoc CCF.kv
55
67
  */
package/kv.js CHANGED
@@ -22,6 +22,17 @@
22
22
  * foo.set("key-1", {"prop1": 42});
23
23
  * ```
24
24
  *
25
+ * Example of using typed access with historical state:
26
+ * ```
27
+ * import * as ccfapp from '@microsoft/ccf-app';
28
+ *
29
+ * const states = ccfapp.getStateRange(handle, begin, end, expiry);
30
+ * // ... error handling ...
31
+ * const firstKv = states[0].kv;
32
+ * const foo = ccfapp.typedKv(firstKv['foo'], ccfapp.string, ccfapp.json);
33
+ * const val = foo.get("key-1");
34
+ * ```
35
+ *
25
36
  * @module
26
37
  */
27
38
  import { ccf } from "./global.js";
@@ -68,12 +79,14 @@ export class TypedKvMap {
68
79
  *
69
80
  * See the {@linkcode converters} module for available converters.
70
81
  *
71
- * @param name The map name in the Key-Value Store.
82
+ * @param nameOrMap Either the map name in the Key-Value Store,
83
+ * or a ``KvMap`` object.
72
84
  * @param kt The converter to use for map keys.
73
85
  * @param vt The converter to use for map values.
74
86
  */
75
- export function typedKv(name, kt, vt) {
76
- return new TypedKvMap(ccf.kv[name], kt, vt);
87
+ export function typedKv(nameOrMap, kt, vt) {
88
+ const kvMap = typeof nameOrMap === "string" ? ccf.kv[nameOrMap] : nameOrMap;
89
+ return new TypedKvMap(kvMap, kt, vt);
77
90
  }
78
91
  /**
79
92
  * @inheritDoc CCF.kv
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microsoft/ccf-app",
3
- "version": "2.0.0-dev4",
3
+ "version": "2.0.0-dev8",
4
4
  "description": "CCF app support package",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -13,23 +13,22 @@
13
13
  "build": "tsc",
14
14
  "test": "cross-env TS_NODE_PROJECT=test/tsconfig.json mocha --loader=ts-node/esm test/**/*.test.ts",
15
15
  "docs": "typedoc",
16
- "docs:serve": "rm -rf html && concurrently \"typedoc --watch --preserveWatchOutput\" \"serve html\""
16
+ "docs:watch": "rm -rf html && typedoc --watch --preserveWatchOutput"
17
17
  },
18
18
  "author": "Microsoft",
19
19
  "license": "Apache-2.0",
20
20
  "devDependencies": {
21
21
  "@types/chai": "^4.2.15",
22
- "@types/mocha": "^8.2.2",
23
- "@types/node": "^14.14.35",
24
- "@types/node-forge": "^0.9.7",
22
+ "@types/mocha": "^9.0.0",
23
+ "@types/node": "^17.0.8",
24
+ "@types/node-forge": "^1.0.0",
25
25
  "chai": "^4.3.4",
26
- "concurrently": "^6.0.0",
26
+ "colors": "1.4.0",
27
27
  "cross-env": "^7.0.3",
28
- "mocha": "^8.3.2",
29
- "node-forge": "^0.10.0",
30
- "serve": "^11.3.2",
31
- "ts-node": "^9.1.1",
32
- "typedoc": "^0.20.34",
33
- "typescript": "4.2.4"
28
+ "mocha": "^9.1.3",
29
+ "node-forge": "^1.2.0",
30
+ "ts-node": "^10.4.0",
31
+ "typedoc": "^0.22.7",
32
+ "typescript": "^4.2.4"
34
33
  }
35
34
  }
package/polyfill.js CHANGED
@@ -29,6 +29,9 @@ class KvMapPolyfill {
29
29
  get(key) {
30
30
  return this.map.get(base64(key));
31
31
  }
32
+ getVersionOfPreviousWrite(key) {
33
+ throw new Error("Not implemented");
34
+ }
32
35
  set(key, value) {
33
36
  this.map.set(base64(key), value);
34
37
  return this;
@@ -60,10 +63,32 @@ class CCFPolyfill {
60
63
  return Reflect.get(target, name, receiver);
61
64
  },
62
65
  });
66
+ this.consensus = {
67
+ getLastCommittedTxId() {
68
+ throw new Error("Not implemented");
69
+ },
70
+ getStatusForTxId(view, seqno) {
71
+ throw new Error("Not implemented");
72
+ },
73
+ getViewForSeqno(seqno) {
74
+ throw new Error("Not implemented");
75
+ },
76
+ };
77
+ this.historical = {
78
+ getStateRange(handle, startSeqno, endSeqno, secondsUntilExpiry) {
79
+ throw new Error("Not implemented");
80
+ },
81
+ dropCachedStates(handle) {
82
+ throw new Error("Not implemented");
83
+ },
84
+ };
63
85
  this.rpc = {
64
86
  setApplyWrites(force) {
65
87
  throw new Error("Not implemented");
66
88
  },
89
+ setClaimsDigest(digest) {
90
+ throw new Error("Not implemented");
91
+ },
67
92
  };
68
93
  this.crypto = {
69
94
  verifySignature(algorithm, key, signature, data) {