@agoric/swingset-vat 0.32.3-dev-0b910ea.0.0b910ea → 0.32.3-dev-df9efa9.0.df9efa9
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 +70 -131
- package/package.json +12 -12
- package/src/controller/controller.js +7 -3
- package/src/kernel/kernel.js +11 -7
- package/tools/vat.js +90 -23
package/README.md
CHANGED
|
@@ -2,32 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
[![License][license-image]][license-url]
|
|
4
4
|
|
|
5
|
-
This repository
|
|
6
|
-
|
|
7
|
-
top of a "kernel" as if they were userspace processes in an operating system.
|
|
5
|
+
This repository implements an architecture in which Vats run on top of a
|
|
6
|
+
"kernel" as if they were userspace processes in an operating system.
|
|
8
7
|
Each Vat gets access to a "syscall" object, through which it can send
|
|
9
8
|
messages into the kernel. Vats receive message from the kernel via a
|
|
10
9
|
"dispatch" function which they register at startup.
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
One such mechanism is implemented so far, named "live slots", but we know
|
|
14
|
-
this is insufficient to provide persistence across restarts.
|
|
11
|
+
See [docs](./docs) for more information.
|
|
15
12
|
|
|
16
|
-
|
|
13
|
+
[SwingSet Runner](../swingset-runner) can be used to explore a SwingSet
|
|
14
|
+
instance, as can the simple `vat` utility in this package.
|
|
15
|
+
|
|
16
|
+
## `vat` CLI
|
|
17
17
|
|
|
18
18
|
```console
|
|
19
|
-
$
|
|
20
|
-
$
|
|
21
|
-
$ bin/vat run demo/encouragementBot
|
|
19
|
+
$ yarn install
|
|
20
|
+
$ bin/vat run --config demo/encouragementBot/swingset.json
|
|
22
21
|
```
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
expected to stabilize for a while.
|
|
26
|
-
|
|
27
|
-
## REPL Shell
|
|
23
|
+
### REPL Shell
|
|
28
24
|
|
|
29
25
|
```console
|
|
30
|
-
$ bin/vat shell demo/encouragementBot
|
|
26
|
+
$ bin/vat shell --config demo/encouragementBot/swingset.json
|
|
31
27
|
vat>
|
|
32
28
|
```
|
|
33
29
|
|
|
@@ -36,13 +32,13 @@ arguments. All vats are loaded, and three additional commands are added to
|
|
|
36
32
|
the environment:
|
|
37
33
|
|
|
38
34
|
* `dump()`: display the kernel tables, including the run queue
|
|
39
|
-
* `step()`: execute the next action on the run queue
|
|
40
|
-
* `run()`: keep stepping until the run queue is empty
|
|
35
|
+
* `await step()`: execute the next action on the run queue
|
|
36
|
+
* `await run()`: keep stepping until the run queue is empty
|
|
41
37
|
|
|
42
|
-
|
|
38
|
+
### Vat Basedirs
|
|
43
39
|
|
|
44
|
-
|
|
45
|
-
the Vats that should be loaded
|
|
40
|
+
`vat` must be called with either a SwingSet configuration file (as above) or a
|
|
41
|
+
"basedir" argument containing sources for all the Vats that should be loaded.
|
|
46
42
|
|
|
47
43
|
Every file named `vat-*.js` (e.g. `vat-foo.js` and `vat-bar-none.js`) will
|
|
48
44
|
create a new Vat (with names like `foo` and `bar-none`). Each directory named
|
|
@@ -53,7 +49,7 @@ In addition, a file named `bootstrap.js` must be present. This will contain
|
|
|
53
49
|
the source for the "bootstrap Vat", which behaves like a regular Vat except:
|
|
54
50
|
|
|
55
51
|
* At startup, its `bootstrap` method will be invoked, as `bootstrap(argv, vats)`
|
|
56
|
-
* The `argv` value will be an array of strings
|
|
52
|
+
* The `argv` value will be an array of strings from the command line. So
|
|
57
53
|
running `bin/vat BASEDIR -- x1 x2 x3` will set `argv = ['x1', 'x2', 'x3']`.
|
|
58
54
|
* The `vats` value will be an object with keys named after the other Vats
|
|
59
55
|
that were created, and values which are each a Presence for that Vat's root
|
|
@@ -69,18 +65,23 @@ anything else.
|
|
|
69
65
|
## Vat Sources
|
|
70
66
|
|
|
71
67
|
Each Vat source file (like `vat-foo.js` or `vat-bar.js`) is treated as a
|
|
72
|
-
starting point for
|
|
68
|
+
starting point for a bundling process that converts the Vat's source tree
|
|
73
69
|
into a single string (so it can be evaluated in a SES realm). This starting
|
|
74
70
|
point can use `import` to reference shared local files. No
|
|
75
71
|
non-local imports are allowed yet.
|
|
76
72
|
|
|
77
|
-
The source file is expected to
|
|
78
|
-
|
|
79
|
-
|
|
73
|
+
The source file is expected to export a `buildRootObject` function that returns
|
|
74
|
+
a Vat "root object" managed by [Liveslots](../swingset-liveslots) with which
|
|
75
|
+
other vats can interact. It is also possible to bypass the "Live Slots" layer
|
|
76
|
+
when the vat options include `enableSetup: true` and are associated with a
|
|
77
|
+
`managerType` that supports such bypass (currently limited to "local"), by
|
|
78
|
+
exporting a default function named "setup" to be invoked with a `syscall` object
|
|
79
|
+
and expected to return a `dispatch` object (see
|
|
80
|
+
[BaseVatOptions](./src/types-external.js)).
|
|
80
81
|
|
|
81
82
|
The "Live Slots" layer provides a function to build `dispatch` out of
|
|
82
83
|
`syscall`, as well as a way to register the root object. This requires a few
|
|
83
|
-
lines of boilerplate in the `setup
|
|
84
|
+
lines of boilerplate in the `setup` function.
|
|
84
85
|
|
|
85
86
|
```js
|
|
86
87
|
function buildRootObject(E) {
|
|
@@ -101,143 +102,79 @@ export default function setup(syscall, state, helpers) {
|
|
|
101
102
|
}
|
|
102
103
|
```
|
|
103
104
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
The Live Slots system enables delivery of messages to remote "Callable
|
|
107
|
-
Objects" objects, as long as those objects are of a particular form. All
|
|
108
|
-
Callable Objects must follow these rules:
|
|
109
|
-
|
|
110
|
-
* all enumerable properties must be functions
|
|
111
|
-
* all properties, and the object itself, must be `harden()`ed
|
|
112
|
-
|
|
113
|
-
The system can pass-by-copy "Data Objects" with similar rules:
|
|
105
|
+
### Exposed (pass-by-presence) Objects
|
|
114
106
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
107
|
+
The Live Slots system enables delivery of inbound messages to local
|
|
108
|
+
"[Remotable](https://docs.agoric.com/guides/js-programming/far#pass-styles-and-harden)"
|
|
109
|
+
objects.
|
|
118
110
|
|
|
119
|
-
|
|
111
|
+
### Root Objects
|
|
120
112
|
|
|
121
|
-
The "Root Object" is a
|
|
113
|
+
The "Root Object" is a Remotable object returned by `buildRootObject()`. It
|
|
122
114
|
will be made available to the Bootstrap Vat.
|
|
123
115
|
|
|
124
|
-
|
|
116
|
+
### Sending Messages with Presences
|
|
125
117
|
|
|
126
|
-
|
|
127
|
-
is a special (empty) object that represents
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
can be used to invoke eventual send methods.
|
|
118
|
+
Each Remotable object in the data of inbound messages arrives locally as a
|
|
119
|
+
Presence. This is a special (empty) object that represents a non-local
|
|
120
|
+
Remotable (i.e., a Remotable hosted by a remote Vat), and can be used to send it
|
|
121
|
+
messages via the special `E()` wrapper.
|
|
131
122
|
|
|
132
123
|
Suppose Vat "bob" defines a Root Object with a method named `bar`. The
|
|
133
|
-
bootstrap receives this as `vats.bob`, and can send a message like
|
|
134
|
-
|
|
135
|
-
```js
|
|
136
|
-
function bootstrap(argv, vats) {
|
|
137
|
-
vats.bob~.bar('hello bob');
|
|
138
|
-
}
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
The `~.` operator (pronounced "wavy dot") has the same left-to-right precedence as
|
|
142
|
-
the `.` "dot" operator, so that example is equivalent to
|
|
143
|
-
`HandledPromise.applyMethod(vats.bob, 'bar', ['hello bob'])`.
|
|
144
|
-
|
|
145
|
-
If you are not running under SES and your Javascript environment does not yet support "wavy dot syntax" (i.e. running `"abc"~.[2]` results in a syntax error, not a Promise), then the special `E()` wrapper can be used to get a proxy from which methods can be invoked, which looks like:
|
|
124
|
+
bootstrap vat receives this as `vats.bob`, and can send a message like so:
|
|
146
125
|
|
|
147
126
|
```js
|
|
148
127
|
function bootstrap(argv, vats) {
|
|
149
|
-
E(vats.bob).bar('hello bob');
|
|
128
|
+
void E(vats.bob).bar('hello bob');
|
|
150
129
|
}
|
|
151
130
|
```
|
|
152
131
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
The main purpose of the wavy dot syntax (and E wrapper) is to provide an
|
|
156
|
-
"eventual send" operator, in which the message is always delivered on some
|
|
157
|
-
later turn of the event loop. This happens regardless of whether the target
|
|
158
|
-
is local or in some other Vat:
|
|
159
|
-
|
|
160
|
-
```js
|
|
161
|
-
const t1 = {
|
|
162
|
-
foo() { console.log('foo called'); },
|
|
163
|
-
};
|
|
164
|
-
t1~.foo()
|
|
165
|
-
console.log('wavy dot called');
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
will print:
|
|
169
|
-
|
|
170
|
-
```
|
|
171
|
-
wavy dot called
|
|
172
|
-
foo called
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
This is equivalent to:
|
|
176
|
-
|
|
177
|
-
```js
|
|
178
|
-
HandledPromise.applyMethod(t1, 'foo', [])
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
or
|
|
182
|
-
|
|
183
|
-
```js
|
|
184
|
-
E(t1).foo()
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
## Return Values
|
|
132
|
+
### Return Values
|
|
188
133
|
|
|
189
134
|
Eventual-sends return a Promise for their eventual result:
|
|
190
135
|
|
|
191
136
|
```js
|
|
192
|
-
const fooP = bob
|
|
193
|
-
fooP.then(
|
|
194
|
-
|
|
137
|
+
const fooP = E(bob).foo();
|
|
138
|
+
fooP.then(
|
|
139
|
+
fulfillment => console.log('foo said', fulfillment),
|
|
140
|
+
rejection => console.log('foo errored with', rejection),
|
|
141
|
+
);
|
|
195
142
|
```
|
|
196
143
|
|
|
197
|
-
|
|
144
|
+
### Sending Messages to Promises
|
|
198
145
|
|
|
199
|
-
|
|
200
|
-
method
|
|
201
|
-
to.
|
|
146
|
+
Eventual-sends can target a not-yet-fulfilled Promise, deferring invocation of
|
|
147
|
+
the method until the Promise fulfills to an object.
|
|
202
148
|
|
|
203
|
-
If
|
|
204
|
-
the
|
|
149
|
+
If the target Promise rejects, or fulfills to something other than a Remotable
|
|
150
|
+
or Presence which supports the method, the method is not invoked and the
|
|
151
|
+
eventual-send promise is rejected.
|
|
205
152
|
|
|
206
153
|
```js
|
|
207
154
|
const badP = Promise.reject(Error());
|
|
208
|
-
const p2 = badP
|
|
155
|
+
const p2 = E(badP).foo();
|
|
209
156
|
p2.then(undefined, rej => console.log('rejected', rej));
|
|
210
157
|
// prints 'rejected'
|
|
211
158
|
```
|
|
212
159
|
|
|
213
|
-
|
|
214
|
-
the method delivery will reject with a `TypeError`.
|
|
160
|
+
### Promise Pipelining
|
|
215
161
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
In `fooP = bob~.foo()`, `fooP` represents the (eventual) return value of
|
|
219
|
-
whatever `foo()` executes. If that return value is also a Callable Object, it
|
|
162
|
+
In `fooP = E(bob).foo()`, `fooP` represents the (eventual) return value of
|
|
163
|
+
whatever `foo()` executes. If that return value is also a Remotable, it
|
|
220
164
|
is possible to queue messages to be delivered to that future target. The
|
|
221
|
-
Promise returned by an eventual-send can be used
|
|
165
|
+
Promise returned by an eventual-send can be used as a target itself, and
|
|
222
166
|
the method invoked will be turned into a queued message that won't be
|
|
223
167
|
delivered until the first promise resolves:
|
|
224
168
|
|
|
225
169
|
```js
|
|
226
|
-
const db = databaseServer
|
|
227
|
-
const row = db
|
|
228
|
-
const success = row
|
|
170
|
+
const db = E(databaseServer).openDB();
|
|
171
|
+
const row = E(db).select(criteria)
|
|
172
|
+
const success = E(row).modify(newValue);
|
|
229
173
|
success.then(res => console.log('row modified'));
|
|
230
174
|
```
|
|
231
175
|
|
|
232
176
|
If you don't care about them, the intermediate values can be discarded:
|
|
233
177
|
|
|
234
|
-
```js
|
|
235
|
-
databaseServer~.openDB()~.select(criteria)~.modify(newValue)
|
|
236
|
-
.then(res => console.log('row modified'));
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
This can be done outside of SES in legacy Javascript environments with the `E()` wrapper:
|
|
240
|
-
|
|
241
178
|
```js
|
|
242
179
|
E(E(E(databaseServer).openDB()).select(criteria)).modify(newValue)
|
|
243
180
|
.then(res => console.log('row modified'));
|
|
@@ -246,24 +183,26 @@ E(E(E(databaseServer).openDB()).select(criteria)).modify(newValue)
|
|
|
246
183
|
This sequence could be expressed with plain `then()` clauses, but by chaining
|
|
247
184
|
them together without `then`, the kernel has enough information to
|
|
248
185
|
speculatively deliver the later messages to the Vat in charge of answering
|
|
249
|
-
the earlier messages
|
|
250
|
-
|
|
251
|
-
|
|
186
|
+
the earlier messages (_if the deciding Vat is configured with
|
|
187
|
+
`enablePipelining: true`, which is not currently the case for any
|
|
188
|
+
[Liveslots](../swingset-liveslots) Vats_). This avoids unnecessary roundtrips,
|
|
189
|
+
by sending the later messages during "downtime" while the target Vat thinks
|
|
190
|
+
about the answer to the first one.
|
|
252
191
|
|
|
253
192
|
This drastic reduction in latency is significant when the Vats are far away
|
|
254
193
|
from each other, and the inter-Vat communication delay is large. The SwingSet
|
|
255
194
|
container does not yet provide complete facilities for off-host messaging,
|
|
256
195
|
but once that is implemented, promise pipelining will make a big difference.
|
|
257
196
|
|
|
258
|
-
|
|
197
|
+
### Presence Identity Comparison
|
|
259
198
|
|
|
260
199
|
Presences preserve identity as they move from one Vat to another:
|
|
261
200
|
|
|
262
|
-
* Sending the same
|
|
263
|
-
|
|
201
|
+
* Sending the same Remotable multiple times will deliver the same Presence on
|
|
202
|
+
the receiving Vat
|
|
264
203
|
* Sending a Presence back to its "home Vat" will arrive as the original
|
|
265
|
-
|
|
266
|
-
* Sending a
|
|
204
|
+
Remotable
|
|
205
|
+
* Sending a Remotable to two different Vats will result in Presences
|
|
267
206
|
that cannot be compared directly, because those two Vats can only
|
|
268
207
|
communicate with messages. But if those two Vats both send those Presences
|
|
269
208
|
to a third Vat, they will arrive as the same Presence object
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agoric/swingset-vat",
|
|
3
|
-
"version": "0.32.3-dev-
|
|
3
|
+
"version": "0.32.3-dev-df9efa9.0.df9efa9",
|
|
4
4
|
"description": "Vat/Container Launcher",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"lint:eslint": "yarn run -T eslint ."
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@agoric/xsnap": "0.14.3-dev-
|
|
25
|
+
"@agoric/xsnap": "0.14.3-dev-df9efa9.0.df9efa9",
|
|
26
26
|
"@types/better-sqlite3": "^7.6.13",
|
|
27
27
|
"@types/microtime": "^2.1.0",
|
|
28
28
|
"@types/tmp": "^0.2.0",
|
|
@@ -30,15 +30,15 @@
|
|
|
30
30
|
"ava": "^5.3.0"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@agoric/internal": "0.3.3-dev-
|
|
34
|
-
"@agoric/kmarshal": "0.1.1-dev-
|
|
35
|
-
"@agoric/store": "0.9.3-dev-
|
|
36
|
-
"@agoric/swing-store": "0.9.2-dev-
|
|
37
|
-
"@agoric/swingset-liveslots": "0.10.3-dev-
|
|
38
|
-
"@agoric/swingset-xsnap-supervisor": "0.10.3-dev-
|
|
39
|
-
"@agoric/time": "0.3.3-dev-
|
|
40
|
-
"@agoric/vat-data": "0.5.3-dev-
|
|
41
|
-
"@agoric/xsnap-lockdown": "0.14.1-dev-
|
|
33
|
+
"@agoric/internal": "0.3.3-dev-df9efa9.0.df9efa9",
|
|
34
|
+
"@agoric/kmarshal": "0.1.1-dev-df9efa9.0.df9efa9",
|
|
35
|
+
"@agoric/store": "0.9.3-dev-df9efa9.0.df9efa9",
|
|
36
|
+
"@agoric/swing-store": "0.9.2-dev-df9efa9.0.df9efa9",
|
|
37
|
+
"@agoric/swingset-liveslots": "0.10.3-dev-df9efa9.0.df9efa9",
|
|
38
|
+
"@agoric/swingset-xsnap-supervisor": "0.10.3-dev-df9efa9.0.df9efa9",
|
|
39
|
+
"@agoric/time": "0.3.3-dev-df9efa9.0.df9efa9",
|
|
40
|
+
"@agoric/vat-data": "0.5.3-dev-df9efa9.0.df9efa9",
|
|
41
|
+
"@agoric/xsnap-lockdown": "0.14.1-dev-df9efa9.0.df9efa9",
|
|
42
42
|
"@endo/base64": "^1.0.12",
|
|
43
43
|
"@endo/bundle-source": "^4.1.2",
|
|
44
44
|
"@endo/captp": "^4.4.8",
|
|
@@ -105,5 +105,5 @@
|
|
|
105
105
|
"typeCoverage": {
|
|
106
106
|
"atLeast": 76.59
|
|
107
107
|
},
|
|
108
|
-
"gitHead": "
|
|
108
|
+
"gitHead": "df9efa9cc6831e761d94e3cc74dfb7a9f886cdb1"
|
|
109
109
|
}
|
|
@@ -401,6 +401,10 @@ export async function makeSwingsetController(
|
|
|
401
401
|
|
|
402
402
|
slogDuration,
|
|
403
403
|
|
|
404
|
+
dumpLog(idx) {
|
|
405
|
+
return deepCopyJsonable(kernel.dumpLog(idx));
|
|
406
|
+
},
|
|
407
|
+
|
|
404
408
|
dump() {
|
|
405
409
|
return deepCopyJsonable(kernel.dump());
|
|
406
410
|
},
|
|
@@ -617,7 +621,7 @@ export async function makeSwingsetController(
|
|
|
617
621
|
* the two stages; this can happen, for example, in some debugging cases.
|
|
618
622
|
*
|
|
619
623
|
* @param {SwingSetConfig} config
|
|
620
|
-
* @param {string[]}
|
|
624
|
+
* @param {string[]} bootstrapArgs
|
|
621
625
|
* @param {{
|
|
622
626
|
* kernelStorage?: SwingStoreKernelStorage;
|
|
623
627
|
* env?: Record<string, string>;
|
|
@@ -634,7 +638,7 @@ export async function makeSwingsetController(
|
|
|
634
638
|
*/
|
|
635
639
|
export async function buildVatController(
|
|
636
640
|
config,
|
|
637
|
-
|
|
641
|
+
bootstrapArgs = [],
|
|
638
642
|
runtimeOptions = {},
|
|
639
643
|
deviceEndowments = {},
|
|
640
644
|
) {
|
|
@@ -670,7 +674,7 @@ export async function buildVatController(
|
|
|
670
674
|
if (!swingsetIsInitialized(kernelStorage)) {
|
|
671
675
|
bootstrapResult = await initializeSwingset(
|
|
672
676
|
config,
|
|
673
|
-
|
|
677
|
+
bootstrapArgs,
|
|
674
678
|
kernelStorage,
|
|
675
679
|
initializationOptions,
|
|
676
680
|
runtimeOptions,
|
package/src/kernel/kernel.js
CHANGED
|
@@ -191,8 +191,8 @@ export default function buildKernel(
|
|
|
191
191
|
|
|
192
192
|
// This is a low-level output-only string logger used by old unit tests to
|
|
193
193
|
// see whether vats made progress or not. The array it appends to is
|
|
194
|
-
// available
|
|
195
|
-
// 'result' value returned by c.queueToKref()
|
|
194
|
+
// available from controller methods `dumpLog` and `dump`. New unit tests
|
|
195
|
+
// should instead use the 'result' value returned by c.queueToKref().
|
|
196
196
|
function testLog(...args) {
|
|
197
197
|
const rendered = args.map(arg =>
|
|
198
198
|
typeof arg === 'string' ? arg : JSON.stringify(arg, abbreviateReplacer),
|
|
@@ -2310,14 +2310,18 @@ export default function buildKernel(
|
|
|
2310
2310
|
};
|
|
2311
2311
|
},
|
|
2312
2312
|
|
|
2313
|
+
// Note: Logs are not deterministic, since log() does not go through the
|
|
2314
|
+
// syscall interface (and we replay transcripts one vat at a time, so any
|
|
2315
|
+
// log() calls that were interleaved during their original execution will be
|
|
2316
|
+
// sorted by vat in the replay). They are therefore not kept in the
|
|
2317
|
+
// persistent state and remain ephemeral.
|
|
2318
|
+
dumpLog(idx = 0) {
|
|
2319
|
+
return ephemeral.log.slice(idx);
|
|
2320
|
+
},
|
|
2313
2321
|
dump() {
|
|
2314
|
-
// note: dump().log is not deterministic, since log() does not go
|
|
2315
|
-
// through the syscall interface (and we replay transcripts one vat at
|
|
2316
|
-
// a time, so any log() calls that were interleaved during their
|
|
2317
|
-
// original execution will be sorted by vat in the replay). Logs are
|
|
2318
|
-
// not kept in the persistent state, only in ephemeral state.
|
|
2319
2322
|
return { log: ephemeral.log, ...kernelKeeper.dump() };
|
|
2320
2323
|
},
|
|
2324
|
+
|
|
2321
2325
|
kdebugEnable,
|
|
2322
2326
|
|
|
2323
2327
|
addImport,
|
package/tools/vat.js
CHANGED
|
@@ -1,43 +1,98 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
|
+
import yargsParser from 'yargs-parser';
|
|
3
5
|
import '@endo/init/pre-bundle-source.js';
|
|
4
6
|
import '@endo/init';
|
|
5
7
|
import process from 'process';
|
|
6
8
|
import repl from 'repl';
|
|
7
9
|
import util from 'util';
|
|
8
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
loadSwingsetConfigFile,
|
|
12
|
+
loadBasedir,
|
|
13
|
+
buildVatController,
|
|
14
|
+
} from '../src/index.js';
|
|
9
15
|
import { buildLoopbox } from '../src/devices/loopbox/loopbox.js';
|
|
10
16
|
|
|
17
|
+
const USAGE = `
|
|
18
|
+
Usage: ${process.argv[1]} \\
|
|
19
|
+
{run|shell} [{-c|--config} <config file>] [<base dir>] [--] [<bootstrap arg>]...
|
|
20
|
+
`.trim();
|
|
21
|
+
|
|
22
|
+
const showUsage = (message, exitCode = 64) => {
|
|
23
|
+
const log = message ? console.error : console.log;
|
|
24
|
+
if (message) log(`${message}\n`);
|
|
25
|
+
log(USAGE);
|
|
26
|
+
return exitCode;
|
|
27
|
+
};
|
|
28
|
+
|
|
11
29
|
function deepLog(item) {
|
|
12
30
|
console.log(util.inspect(item, false, null, true));
|
|
13
31
|
}
|
|
14
32
|
|
|
15
33
|
async function main() {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
34
|
+
const {
|
|
35
|
+
_: argv,
|
|
36
|
+
'--': extraArgs = [],
|
|
37
|
+
...options
|
|
38
|
+
} = yargsParser(process.argv.slice(2), {
|
|
39
|
+
configuration: { 'populate--': true, 'unknown-options-as-args': true },
|
|
40
|
+
string: ['config'],
|
|
41
|
+
alias: { config: ['c'] },
|
|
42
|
+
});
|
|
22
43
|
const command = argv.shift();
|
|
23
|
-
if (command
|
|
24
|
-
|
|
44
|
+
if (command === undefined) {
|
|
45
|
+
if (argv.includes('--help')) return showUsage();
|
|
46
|
+
return showUsage('Missing command');
|
|
47
|
+
} else if (command !== 'run' && command !== 'shell') {
|
|
48
|
+
return showUsage(
|
|
49
|
+
command === 'help' ? undefined : `Unrecognized command: ${command}`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
const configPath = options.config;
|
|
53
|
+
let basedir = /** @type {string | undefined} */ (argv.shift());
|
|
54
|
+
if (basedir === undefined) {
|
|
55
|
+
basedir = configPath ? dirname(configPath) : '.';
|
|
25
56
|
}
|
|
26
|
-
const
|
|
27
|
-
argv[0] === '--' || argv[0] === undefined ? '.' : argv.shift();
|
|
28
|
-
const vatArgv = argv[0] === '--' ? argv.slice(1) : argv;
|
|
57
|
+
const vatArgv = /** @type {string[]} */ ([...argv, ...extraArgs]);
|
|
29
58
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
config
|
|
59
|
+
const config = await (options.config
|
|
60
|
+
? loadSwingsetConfigFile(options.config)
|
|
61
|
+
: loadBasedir(basedir));
|
|
62
|
+
assert(config);
|
|
63
|
+
config.devices ||= {};
|
|
64
|
+
const deviceEndowments = /** @type {Record<string, unknown>} */ ({});
|
|
65
|
+
// @ts-expect-error TS2339: Property 'loopboxSenders' does not exist on type 'SwingSetConfig'.
|
|
66
|
+
const { loopboxSenders } = config;
|
|
67
|
+
// @ts-expect-error
|
|
68
|
+
delete config.loopboxSenders;
|
|
69
|
+
if (loopboxSenders) {
|
|
70
|
+
const { loopboxSrcPath, loopboxEndowments } = buildLoopbox('immediate');
|
|
71
|
+
config.devices.loopbox = {
|
|
72
|
+
sourceSpec: loopboxSrcPath,
|
|
73
|
+
parameters: { senders: loopboxSenders },
|
|
74
|
+
};
|
|
75
|
+
deviceEndowments.loopbox = { ...loopboxEndowments };
|
|
76
|
+
}
|
|
34
77
|
|
|
35
|
-
|
|
36
|
-
|
|
78
|
+
const controller = await buildVatController(
|
|
79
|
+
config,
|
|
80
|
+
vatArgv,
|
|
81
|
+
{},
|
|
82
|
+
deviceEndowments,
|
|
83
|
+
);
|
|
37
84
|
if (command === 'run') {
|
|
38
85
|
await controller.run();
|
|
86
|
+
for (const entry of controller.dumpLog()) console.log(entry);
|
|
39
87
|
console.log('= vat finished');
|
|
40
88
|
} else if (command === 'shell') {
|
|
89
|
+
let logIndex = 0;
|
|
90
|
+
const dumpLog = () => {
|
|
91
|
+
for (const entry of controller.dumpLog(logIndex)) {
|
|
92
|
+
logIndex += 1;
|
|
93
|
+
console.log(entry);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
41
96
|
const r = repl.start({ prompt: 'vat> ', replMode: repl.REPL_MODE_STRICT });
|
|
42
97
|
r.context.dump = () => {
|
|
43
98
|
const d = controller.dump();
|
|
@@ -51,15 +106,27 @@ async function main() {
|
|
|
51
106
|
deepLog(d.acceptanceQueue);
|
|
52
107
|
};
|
|
53
108
|
r.context.dump2 = () => controller.dump();
|
|
54
|
-
r.context.run = () => {
|
|
109
|
+
r.context.run = async () => {
|
|
55
110
|
console.log('run!');
|
|
56
|
-
|
|
111
|
+
const result = await controller.run();
|
|
112
|
+
dumpLog();
|
|
113
|
+
return result;
|
|
57
114
|
};
|
|
58
|
-
r.context.step = () => {
|
|
115
|
+
r.context.step = async () => {
|
|
59
116
|
console.log('step!');
|
|
60
|
-
|
|
117
|
+
const result = await controller.step();
|
|
118
|
+
dumpLog();
|
|
119
|
+
return result;
|
|
61
120
|
};
|
|
62
121
|
}
|
|
63
122
|
}
|
|
64
123
|
|
|
65
|
-
|
|
124
|
+
main().then(
|
|
125
|
+
exitCode => {
|
|
126
|
+
if (exitCode !== undefined) process.exitCode ||= exitCode;
|
|
127
|
+
},
|
|
128
|
+
err => {
|
|
129
|
+
console.error(err);
|
|
130
|
+
process.exitCode ||= 1;
|
|
131
|
+
},
|
|
132
|
+
);
|