@@ -5,7 +5,6 @@ import { test } from '../../tools/prepare-test-env-ava.js';
5
5
import path from 'path' ;
6
6
import bundleSource from '@agoric/bundle-source' ;
7
7
import { provideHostStorage } from '../../src/hostStorage.js' ;
8
-
9
8
import {
10
9
initializeSwingset ,
11
10
makeSwingsetController ,
@@ -15,6 +14,7 @@ import {
15
14
buildMailboxStateMap ,
16
15
buildMailbox ,
17
16
} from '../../src/devices/mailbox.js' ;
17
+ import { capargs } from '../util.js' ;
18
18
19
19
test . before ( async t => {
20
20
const kernelBundles = await buildKernelBundles ( ) ;
@@ -91,106 +91,140 @@ test('mailbox inbound', async t => {
91
91
mailbox : { ...mb . endowments } ,
92
92
} ;
93
93
94
- let rc ;
95
-
96
94
const hostStorage = provideHostStorage ( ) ;
97
95
await initializeSwingset ( config , [ 'mailbox2' ] , hostStorage , t . context . data ) ;
98
96
const c = await makeSwingsetController ( hostStorage , deviceEndowments ) ;
99
97
await c . run ( ) ;
100
- rc = mb . deliverInbound (
101
- 'peer1' ,
102
- [
103
- [ 1 , 'msg1' ] ,
104
- [ 2 , 'msg2' ] ,
105
- ] ,
106
- 0 ,
107
- ) ;
108
- t . truthy ( rc ) ;
98
+ const m1 = [ 1 , 'msg1' ] ;
99
+ const m2 = [ 2 , 'msg2' ] ;
100
+ const m3 = [ 3 , 'msg3' ] ;
101
+ t . true ( mb . deliverInbound ( 'peer1' , [ m1 , m2 ] , 0 ) ) ;
109
102
await c . run ( ) ;
110
- t . deepEqual ( c . dump ( ) . log , [ 'dm-peer1' , 'm-1-msg1' , 'm-2-msg2' ] ) ;
111
-
112
- // delivering the same messages should not trigger sends, but the ack is new
113
- rc = mb . deliverInbound (
114
- 'peer1' ,
115
- [
116
- [ 1 , 'msg1' ] ,
117
- [ 2 , 'msg2' ] ,
118
- ] ,
119
- 3 ,
120
- ) ;
121
- t . truthy ( rc ) ;
103
+ const expected = [ 'dm-peer1' , 'm-1-msg1' , 'm-2-msg2' , 'da-peer1-0' ] ;
104
+ t . deepEqual ( c . dump ( ) . log , expected ) ;
105
+
106
+ // all messages/acks should be delivered, even duplicates
107
+ t . true ( mb . deliverInbound ( 'peer1' , [ m1 , m2 ] , 0 ) ) ;
122
108
await c . run ( ) ;
123
- t . deepEqual ( c . dump ( ) . log , [ 'dm-peer1' , 'm-1-msg1' , 'm-2-msg2' , 'da-peer1-3' ] ) ;
124
-
125
- // no new messages/acks makes deliverInbound return 'false'
126
- rc = mb . deliverInbound (
127
- 'peer1' ,
128
- [
129
- [ 1 , 'msg1' ] ,
130
- [ 2 , 'msg2' ] ,
131
- ] ,
132
- 3 ,
133
- ) ;
134
- t . falsy ( rc ) ;
109
+ expected . push ( ...[ 'dm-peer1' , 'm-1-msg1' , 'm-2-msg2' , 'da-peer1-0' ] ) ;
110
+ t . deepEqual ( c . dump ( ) . log , expected ) ;
111
+
112
+ // new messages too
113
+ t . true ( mb . deliverInbound ( 'peer1' , [ m1 , m2 , m3 ] , 0 ) ) ;
135
114
await c . run ( ) ;
136
- t . deepEqual ( c . dump ( ) . log , [ 'dm-peer1' , 'm-1-msg1' , 'm-2-msg2' , 'da-peer1-3' ] ) ;
137
-
138
- // but new messages should be sent
139
- rc = mb . deliverInbound (
140
- 'peer1' ,
141
- [
142
- [ 1 , 'msg1' ] ,
143
- [ 2 , 'msg2' ] ,
144
- [ 3 , 'msg3' ] ,
145
- ] ,
146
- 3 ,
115
+ expected . push (
116
+ ...[ 'dm-peer1' , 'm-1-msg1' , 'm-2-msg2' , 'm-3-msg3' , 'da-peer1-0' ] ,
147
117
) ;
148
- t . truthy ( rc ) ;
118
+ t . deepEqual ( c . dump ( ) . log , expected ) ;
119
+
120
+ // and new ack
121
+ t . true ( mb . deliverInbound ( 'peer1' , [ m1 , m2 , m3 ] , 6 ) ) ;
149
122
await c . run ( ) ;
150
- t . deepEqual ( c . dump ( ) . log , [
151
- 'dm-peer1' ,
152
- 'm-1-msg1' ,
153
- 'm-2-msg2' ,
154
- 'da-peer1-3' ,
155
- 'dm-peer1' ,
156
- 'm-3-msg3' ,
157
- ] ) ;
158
-
159
- // and a higher ack should be sent
160
- rc = mb . deliverInbound (
161
- 'peer1' ,
162
- [
163
- [ 1 , 'msg1' ] ,
164
- [ 2 , 'msg2' ] ,
165
- [ 3 , 'msg3' ] ,
166
- ] ,
167
- 4 ,
123
+ expected . push (
124
+ ...[ 'dm-peer1' , 'm-1-msg1' , 'm-2-msg2' , 'm-3-msg3' , 'da-peer1-6' ] ,
168
125
) ;
169
- t . truthy ( rc ) ;
170
- await c . run ( ) ;
171
- t . deepEqual ( c . dump ( ) . log , [
172
- 'dm-peer1' ,
173
- 'm-1-msg1' ,
174
- 'm-2-msg2' ,
175
- 'da-peer1-3' ,
176
- 'dm-peer1' ,
177
- 'm-3-msg3' ,
178
- 'da-peer1-4' ,
179
- ] ) ;
180
-
181
- rc = mb . deliverInbound ( 'peer2' , [ [ 4 , 'msg4' ] ] , 5 ) ;
182
- t . truthy ( rc ) ;
126
+ t . deepEqual ( c . dump ( ) . log , expected ) ;
127
+ } ) ;
128
+
129
+ async function initializeMailboxKernel ( t ) {
130
+ const s = buildMailboxStateMap ( ) ;
131
+ const mb = buildMailbox ( s ) ;
132
+ const config = {
133
+ bootstrap : 'bootstrap' ,
134
+ vats : {
135
+ bootstrap : {
136
+ bundle : t . context . data . bootstrap ,
137
+ } ,
138
+ } ,
139
+ devices : {
140
+ mailbox : {
141
+ sourceSpec : require . resolve ( mb . srcPath ) ,
142
+ } ,
143
+ } ,
144
+ } ;
145
+ const hostStorage = provideHostStorage ( ) ;
146
+ await initializeSwingset (
147
+ config ,
148
+ [ 'mailbox-determinism' ] ,
149
+ hostStorage ,
150
+ t . context . data ,
151
+ ) ;
152
+ return hostStorage ;
153
+ }
154
+
155
+ async function makeMailboxKernel ( hostStorage ) {
156
+ const s = buildMailboxStateMap ( ) ;
157
+ const mb = buildMailbox ( s ) ;
158
+ const deviceEndowments = {
159
+ mailbox : { ...mb . endowments } ,
160
+ } ;
161
+ const c = await makeSwingsetController ( hostStorage , deviceEndowments ) ;
162
+ c . pinVatRoot ( 'bootstrap' ) ;
183
163
await c . run ( ) ;
184
- t . deepEqual ( c . dump ( ) . log , [
185
- 'dm-peer1' ,
186
- 'm-1-msg1' ,
187
- 'm-2-msg2' ,
188
- 'da-peer1-3' ,
189
- 'dm-peer1' ,
190
- 'm-3-msg3' ,
191
- 'da-peer1-4' ,
192
- 'dm-peer2' ,
193
- 'm-4-msg4' ,
194
- 'da-peer2-5' ,
195
- ] ) ;
164
+ return [ c , mb ] ;
165
+ }
166
+
167
+ test ( 'mailbox determinism' , async t => {
168
+ // we run two kernels in parallel
169
+ const hostStorage1 = await initializeMailboxKernel ( t ) ;
170
+ const hostStorage2 = await initializeMailboxKernel ( t ) ;
171
+ const [ c1a , mb1a ] = await makeMailboxKernel ( hostStorage1 ) ;
172
+ const [ c2 , mb2 ] = await makeMailboxKernel ( hostStorage2 ) ;
173
+
174
+ // they get the same inbound message
175
+ const msg1 = [ [ 1 , 'msg1' ] ] ;
176
+ t . true ( mb1a . deliverInbound ( 'peer1' , msg1 , 0 ) ) ;
177
+ await c1a . run ( ) ;
178
+ t . deepEqual ( c1a . dump ( ) . log , [ 'comms receive msg1' ] ) ;
179
+ const kp1 = c1a . queueToVatRoot ( 'bootstrap' , 'getNumReceived' , capargs ( [ ] ) ) ;
180
+ await c1a . run ( ) ;
181
+ t . deepEqual ( JSON . parse ( c1a . kpResolution ( kp1 ) . body ) , 1 ) ;
182
+
183
+ t . true ( mb2 . deliverInbound ( 'peer1' , msg1 , 0 ) ) ;
184
+ await c2 . run ( ) ;
185
+ t . deepEqual ( c2 . dump ( ) . log , [ 'comms receive msg1' ] ) ;
186
+ const kp2 = c2 . queueToVatRoot ( 'bootstrap' , 'getNumReceived' , capargs ( [ ] ) ) ;
187
+ await c2 . run ( ) ;
188
+ t . deepEqual ( JSON . parse ( c2 . kpResolution ( kp2 ) . body ) , 1 ) ;
189
+
190
+ // both should have the same number of cranks
191
+ t . is (
192
+ hostStorage1 . kvStore . get ( 'crankNumber' ) ,
193
+ hostStorage2 . kvStore . get ( 'crankNumber' ) ,
194
+ ) ;
195
+
196
+ // then one is restarted, but the other keeps running
197
+ const [ c1b , mb1b ] = await makeMailboxKernel ( hostStorage1 ) ;
198
+
199
+ // Now we repeat delivery of that message to both. The mailbox should send
200
+ // it to vattp, even though it's duplicate, because the mailbox doesn't
201
+ // have durable state, and cannot correctly (deterministically) tell that
202
+ // it's a duplicate.
203
+ t . true ( mb1b . deliverInbound ( 'peer1' , msg1 , 0 ) ) ;
204
+ await c1b . run ( ) ;
205
+ // the testlog is part of the ephemeral kernel state, so it will only have
206
+ // a record of messages in the second run, however the vat is replayed
207
+ // during the second-run startup, so we expect to see one copy of the
208
+ // original message, delivered during the second run
209
+ t . deepEqual ( c1b . dump ( ) . log , [ 'comms receive msg1' ] ) ;
210
+ // but vattp dedups, so only one message should be delivered to comms
211
+ const kp3 = c1b . queueToVatRoot ( 'bootstrap' , 'getNumReceived' , capargs ( [ ] ) ) ;
212
+ await c1b . run ( ) ;
213
+ t . deepEqual ( JSON . parse ( c1b . kpResolution ( kp3 ) . body ) , 1 ) ;
214
+
215
+ t . true ( mb2 . deliverInbound ( 'peer1' , msg1 , 0 ) ) ;
216
+ await c2 . run ( ) ;
217
+ // the second kernel still has that ephemeral testlog, however the vat is
218
+ // still running, so we only see the original message from the first run
219
+ t . deepEqual ( c2 . dump ( ) . log , [ 'comms receive msg1' ] ) ;
220
+ const kp4 = c2 . queueToVatRoot ( 'bootstrap' , 'getNumReceived' , capargs ( [ ] ) ) ;
221
+ await c2 . run ( ) ;
222
+ t . deepEqual ( JSON . parse ( c2 . kpResolution ( kp4 ) . body ) , 1 ) ;
223
+
224
+ // Both should *still* have the same number of cranks. This is what bug
225
+ // #3471 exposed.
226
+ t . is (
227
+ hostStorage1 . kvStore . get ( 'crankNumber' ) ,
228
+ hostStorage2 . kvStore . get ( 'crankNumber' ) ,
229
+ ) ;
196
230
} ) ;
0 commit comments