@river-build/sdk
Version:
For more details, visit the following resources:
155 lines • 7.23 kB
JavaScript
/**
* @group main
*/
import { MembershipOp, SyncOp } from '@river-build/proto';
import { dlog } from '@river-build/dlog';
import { makeUniqueChannelStreamId, makeUserStreamId, streamIdToBytes, userIdFromAddress, } from '../../id';
import { makeEvent, unpackStream, unpackStreamEnvelopes } from '../../sign';
import { getMessagePayload, getMiniblockHeader, make_ChannelPayload_Inception, make_ChannelPayload_Message, make_MemberPayload_Membership2, make_SpacePayload_Inception, make_UserPayload_Inception, } from '../../types';
import { TEST_ENCRYPTED_MESSAGE_PROPS, makeRandomUserContext, makeTestRpcClient, makeUniqueSpaceStreamId, iterableWrapper, } from '../testUtils';
const log = dlog('csb:test:syncWithBlocks');
describe('syncWithBlocks', () => {
let bobsContext;
beforeEach(async () => {
bobsContext = await makeRandomUserContext();
});
test('blocksGetGeneratedAndSynced', async () => {
log('start');
const bob = await makeTestRpcClient();
const bobsUserId = userIdFromAddress(bobsContext.creatorAddress);
const bobsUserStreamIdStr = makeUserStreamId(bobsUserId);
const bobsUserStreamId = streamIdToBytes(bobsUserStreamIdStr);
await bob.createStream({
events: [
await makeEvent(bobsContext, make_UserPayload_Inception({
streamId: bobsUserStreamId,
})),
],
streamId: bobsUserStreamId,
});
log('Bob created user, about to create space');
// Bob creates space and channel
const spacedStreamIdStr = makeUniqueSpaceStreamId();
const spacedStreamId = streamIdToBytes(spacedStreamIdStr);
const spaceInceptionEvent = await makeEvent(bobsContext, make_SpacePayload_Inception({
streamId: spacedStreamId,
}));
await bob.createStream({
events: [
spaceInceptionEvent,
await makeEvent(bobsContext, make_MemberPayload_Membership2({
userId: bobsUserId,
op: MembershipOp.SO_JOIN,
initiatorId: bobsUserId,
})),
],
streamId: spacedStreamId,
});
const channelIdStr = makeUniqueChannelStreamId(spacedStreamIdStr);
const channelId = streamIdToBytes(channelIdStr);
const channelInceptionEvent = await makeEvent(bobsContext, make_ChannelPayload_Inception({
streamId: channelId,
spaceId: spacedStreamId,
}));
const channelJoinEvent = await makeEvent(bobsContext, make_MemberPayload_Membership2({
userId: bobsUserId,
op: MembershipOp.SO_JOIN,
initiatorId: bobsUserId,
}));
let nextHash = channelJoinEvent.hash;
const channelEvents = [channelInceptionEvent, channelJoinEvent];
log('creating channel with events=', channelEvents);
await bob.createStream({
events: channelEvents,
streamId: channelId,
});
log('Bob created channel, reads it back');
const channel = await bob.getStream({ streamId: channelId });
expect(channel).toBeDefined();
expect(channel.stream).toBeDefined();
expect(channel.stream?.nextSyncCookie?.streamId).toEqual(channelId);
// Last event must be a genesis miniblock header.
const events = (await unpackStream(channel.stream, undefined)).streamAndCookie.miniblocks.flatMap((mb) => mb.events);
const lastEvent = events.at(-1);
const miniblockHeader = getMiniblockHeader(lastEvent);
expect(miniblockHeader).toBeDefined();
expect(miniblockHeader?.miniblockNum).toEqual(0n);
expect(miniblockHeader?.eventHashes).toHaveLength(2);
const knownHashes = new Set(events.map((e) => e.hashStr));
// Post a message to the channel
let text = 'hello ';
const messageEvent = await makeEvent(bobsContext, make_ChannelPayload_Message({
...TEST_ENCRYPTED_MESSAGE_PROPS,
ciphertext: text,
}), channel.stream?.miniblocks.at(-1)?.header?.hash);
nextHash = messageEvent.hash;
const resp = await bob.addEvent({
streamId: channelId,
event: messageEvent,
});
log('addEvent response', { resp });
// Bob starts sync on the channel
const syncStream = bob.syncStreams({
syncPos: [channel.stream.nextSyncCookie],
}, { timeoutMs: -1 });
// If there is a message, next expect a miniblock header, and vise versa.
let expectMessage = true;
let blocksSeen = 0;
log('===================syncing===================');
for await (const res of iterableWrapper(syncStream)) {
if (res.syncOp === SyncOp.SYNC_CLOSE) {
// done with sync
break;
}
if (res.syncOp !== SyncOp.SYNC_UPDATE || !res.stream) {
// skip non-stream cookie responses
continue;
}
const stream = res.stream;
expect(stream).toBeDefined();
const parsed = await unpackStreamEnvelopes(res.stream, undefined);
log('===================sunk===================', { parsed });
for (const p of parsed) {
if (knownHashes.has(p.hashStr)) {
continue;
}
knownHashes.add(p.hashStr);
if (expectMessage) {
const message = getMessagePayload(p);
expect(message).toBeDefined();
expect(message?.ciphertext).toEqual(text);
log('messageSeen', { message });
expectMessage = false;
}
else {
const miniblockHeader = getMiniblockHeader(p);
expect(miniblockHeader).toBeDefined();
expect(miniblockHeader?.miniblockNum).toEqual(BigInt(blocksSeen + 1));
expect(miniblockHeader?.eventHashes).toHaveLength(1);
expect(miniblockHeader?.eventHashes[0]).toEqual(nextHash);
if (blocksSeen > 10) {
log('cancel sync');
await bob.cancelSync({ syncId: res.syncId });
break;
}
expectMessage = true;
text = `${text} ${blocksSeen}`;
log('expectMessgage', { text });
blocksSeen++;
const messageEvent = await makeEvent(bobsContext, make_ChannelPayload_Message({
...TEST_ENCRYPTED_MESSAGE_PROPS,
ciphertext: text,
}), p.hash);
nextHash = messageEvent.hash;
const response = await bob.addEvent({
streamId: channelId,
event: messageEvent,
});
log('addEvent response', { response });
}
}
}
log('done');
});
});
//# sourceMappingURL=syncWithBlocks.test.js.map