@fireproof/core 0.7.3-dev.2 → 0.8.0-dev.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,144 @@
1
+ import * as CBW from '@ipld/car/buffer-writer';
2
+ import * as raw from 'multiformats/codecs/raw';
3
+ import { encrypt, decrypt } from '../crypto.js';
4
+ import { parse } from 'multiformats/link';
5
+ import { sha256 } from 'multiformats/hashes/sha2';
6
+ import * as Block from 'multiformats/block';
7
+ import { Buffer } from 'buffer';
8
+ // @ts-ignore
9
+ import { bf } from 'prolly-trees/utils';
10
+ // @ts-ignore
11
+ import { nocache as cache } from 'prolly-trees/cache';
12
+ const chunker = bf(30);
13
+ export async function getEmptyLoader() {
14
+ const theseWriteableBlocks = new VMemoryBlockstore();
15
+ return {
16
+ blocks: theseWriteableBlocks,
17
+ put: async (cid, bytes) => {
18
+ return await theseWriteableBlocks.put(cid, bytes);
19
+ },
20
+ get: async (cid) => {
21
+ const got = await theseWriteableBlocks.get(cid);
22
+ return got.bytes;
23
+ }
24
+ };
25
+ }
26
+ export class VMemoryBlockstore {
27
+ /** @type {Map<string, Uint8Array>} */
28
+ blocks = new Map();
29
+ instanceId = Math.random().toString(36).slice(2);
30
+ async get(cid) {
31
+ const bytes = this.blocks.get(cid.toString());
32
+ if (!bytes)
33
+ throw new Error('block not found ' + cid.toString());
34
+ return { cid, bytes };
35
+ }
36
+ /**
37
+ * @param {any} cid
38
+ * @param {Uint8Array} bytes
39
+ */
40
+ async put(cid, bytes) {
41
+ this.blocks.set(cid.toString(), bytes);
42
+ }
43
+ *entries() {
44
+ for (const [str, bytes] of this.blocks) {
45
+ yield { cid: parse(str), bytes };
46
+ }
47
+ }
48
+ }
49
+ export const blocksToCarBlock = async (rootCids, blocks) => {
50
+ // console.log('blocksToCarBlock', rootCids, blocks.constructor.name)
51
+ let size = 0;
52
+ if (!Array.isArray(rootCids)) {
53
+ rootCids = [rootCids];
54
+ }
55
+ const headerSize = CBW.headerLength({ roots: rootCids });
56
+ size += headerSize;
57
+ if (!Array.isArray(blocks)) {
58
+ blocks = Array.from(blocks.entries());
59
+ }
60
+ for (const { cid, bytes } of blocks) {
61
+ // console.log(cid, bytes)
62
+ size += CBW.blockLength({ cid, bytes });
63
+ }
64
+ const buffer = new Uint8Array(size);
65
+ const writer = await CBW.createWriter(buffer, { headerSize });
66
+ for (const cid of rootCids) {
67
+ writer.addRoot(cid);
68
+ }
69
+ for (const { cid, bytes } of blocks) {
70
+ writer.write({ cid, bytes });
71
+ }
72
+ await writer.close();
73
+ return await Block.encode({ value: writer.bytes, hasher: sha256, codec: raw });
74
+ };
75
+ export const blocksToEncryptedCarBlock = async (innerBlockStoreClockRootCid, blocks, keyMaterial, cids) => {
76
+ const encryptionKey = Buffer.from(keyMaterial, 'hex');
77
+ const encryptedBlocks = [];
78
+ const theCids = cids;
79
+ // console.trace('blocksToEncryptedCarBlock', blocks)
80
+ // for (const { cid } of blocks.entries()) {
81
+ // theCids.push(cid.toString())
82
+ // }
83
+ // console.log(
84
+ // 'encrypting',
85
+ // theCids.length,
86
+ // 'blocks',
87
+ // theCids.includes(innerBlockStoreClockRootCid.toString()),
88
+ // keyMaterial
89
+ // )
90
+ // console.log('cids', theCids, innerBlockStoreClockRootCid.toString())
91
+ let last;
92
+ for await (const block of encrypt({
93
+ cids: theCids,
94
+ get: async (cid) => {
95
+ // console.log('getencrypt', cid)
96
+ const got = blocks.get(cid);
97
+ // console.log('got', got)
98
+ return got.block ? ({ cid, bytes: got.block }) : got;
99
+ },
100
+ key: encryptionKey,
101
+ hasher: sha256,
102
+ chunker,
103
+ cache,
104
+ // codec: dagcbor, // should be crypto?
105
+ root: innerBlockStoreClockRootCid
106
+ })) {
107
+ encryptedBlocks.push(block);
108
+ last = block;
109
+ }
110
+ // console.log('last', last.cid.toString(), 'for clock', innerBlockStoreClockRootCid.toString())
111
+ const encryptedCar = await blocksToCarBlock(last.cid, encryptedBlocks);
112
+ return encryptedCar;
113
+ };
114
+ // { root, get, key, cache, chunker, hasher }
115
+ const memoizeDecryptedCarBlocks = new Map();
116
+ export const blocksFromEncryptedCarBlock = async (cid, get, keyMaterial) => {
117
+ if (memoizeDecryptedCarBlocks.has(cid.toString())) {
118
+ return memoizeDecryptedCarBlocks.get(cid.toString());
119
+ }
120
+ else {
121
+ const blocksPromise = (async () => {
122
+ const decryptionKey = Buffer.from(keyMaterial, 'hex');
123
+ // console.log('decrypting', keyMaterial, cid.toString())
124
+ const cids = new Set();
125
+ const decryptedBlocks = [];
126
+ for await (const block of decrypt({
127
+ root: cid,
128
+ get,
129
+ key: decryptionKey,
130
+ chunker,
131
+ hasher: sha256,
132
+ cache
133
+ // codec: dagcbor
134
+ })) {
135
+ // console.log('decrypted', block.cid.toString())
136
+ decryptedBlocks.push(block);
137
+ cids.add(block.cid.toString());
138
+ }
139
+ return { blocks: decryptedBlocks, cids };
140
+ })();
141
+ memoizeDecryptedCarBlocks.set(cid.toString(), blocksPromise);
142
+ return blocksPromise;
143
+ }
144
+ };
@@ -52,7 +52,7 @@ export class Browser extends Base {
52
52
  if (this.config.readonly)
53
53
  return;
54
54
  try {
55
- return localStorage.setItem(this.headerKey(branch), this.prepareHeader(header));
55
+ return localStorage.setItem(this.headerKey(branch), header);
56
56
  }
57
57
  catch (e) { }
58
58
  }
@@ -40,12 +40,11 @@ export class Filesystem extends Base {
40
40
  return JSON.parse(header);
41
41
  }
42
42
  async writeHeader(branch, header) {
43
- // console.log('saveHeader', this.isBrowser)
43
+ // console.log('saveHeader fs', header)
44
44
  if (this.config.readonly)
45
45
  return;
46
- const pHeader = this.prepareHeader(header);
47
46
  // console.log('writeHeader fs', branch, pHeader)
48
- await writeSync(this.headerFilename(branch), pHeader);
47
+ await writeSync(this.headerFilename(branch), header);
49
48
  }
50
49
  headerFilename(branch = 'main') {
51
50
  // console.log('headerFilename', this.config.dataDir, this.name)
@@ -45,11 +45,10 @@ export class Rest extends Base {
45
45
  async writeHeader(branch, header) {
46
46
  if (this.config.readonly)
47
47
  return;
48
- const pHeader = this.prepareHeader(header);
49
48
  // console.log('writeHeader rt', branch, pHeader)
50
49
  const response = await fetch(this.headerURL(branch), {
51
50
  method: 'PUT',
52
- body: pHeader,
51
+ body: header,
53
52
  headers: { 'Content-Type': 'application/json' }
54
53
  });
55
54
  if (!response.ok)
@@ -1,40 +0,0 @@
1
- import fetch from 'cross-fetch';
2
- import { Base } from './base.js';
3
- const defaultConfig = {
4
- upload: () => { },
5
- url: (cid) => `https://${cid}.ipfs.w3s.link/`
6
- };
7
- export class UCAN extends Base {
8
- constructor(name, config = {}) {
9
- super(name, Object.assign({}, defaultConfig, config));
10
- }
11
- async writeCars(cars) {
12
- if (this.config.readonly)
13
- return;
14
- for (const { cid, bytes } of cars) {
15
- console.log(`write UCAN ${cid}, ${bytes.length} bytes`);
16
- const upCid = await this.config.upload(bytes);
17
- console.log(`wrote UCAN ${cid}, ${upCid}`);
18
- // if (!response.ok) throw new Error(`An error occurred: ${response.statusText}`)
19
- }
20
- }
21
- async readCar(carCid) {
22
- const carURL = this.config.url(carCid);
23
- const response = await fetch(carURL);
24
- if (!response.ok)
25
- throw new Error(`An error occurred: ${response.statusText}`);
26
- const got = await response.arrayBuffer();
27
- return new Uint8Array(got);
28
- }
29
- async loadHeader(branch = 'main') {
30
- return headerMock.get(branch);
31
- }
32
- async writeHeader(branch, header) {
33
- if (this.config.readonly)
34
- return;
35
- const pHeader = this.prepareHeader(header);
36
- // console.log('writeHeader rt', branch, pHeader)
37
- headerMock.set(branch, pHeader);
38
- }
39
- }
40
- const headerMock = new Map();
@@ -0,0 +1,144 @@
1
+ import * as CBW from '@ipld/car/buffer-writer';
2
+ import * as raw from 'multiformats/codecs/raw';
3
+ import { encrypt, decrypt } from '../crypto.js';
4
+ import { parse } from 'multiformats/link';
5
+ import { sha256 } from 'multiformats/hashes/sha2';
6
+ import * as Block from 'multiformats/block';
7
+ import { Buffer } from 'buffer';
8
+ // @ts-ignore
9
+ import { bf } from 'prolly-trees/utils';
10
+ // @ts-ignore
11
+ import { nocache as cache } from 'prolly-trees/cache';
12
+ const chunker = bf(30);
13
+ export async function getEmptyLoader() {
14
+ const theseWriteableBlocks = new VMemoryBlockstore();
15
+ return {
16
+ blocks: theseWriteableBlocks,
17
+ put: async (cid, bytes) => {
18
+ return await theseWriteableBlocks.put(cid, bytes);
19
+ },
20
+ get: async (cid) => {
21
+ const got = await theseWriteableBlocks.get(cid);
22
+ return got.bytes;
23
+ }
24
+ };
25
+ }
26
+ export class VMemoryBlockstore {
27
+ /** @type {Map<string, Uint8Array>} */
28
+ blocks = new Map();
29
+ instanceId = Math.random().toString(36).slice(2);
30
+ async get(cid) {
31
+ const bytes = this.blocks.get(cid.toString());
32
+ if (!bytes)
33
+ throw new Error('block not found ' + cid.toString());
34
+ return { cid, bytes };
35
+ }
36
+ /**
37
+ * @param {any} cid
38
+ * @param {Uint8Array} bytes
39
+ */
40
+ async put(cid, bytes) {
41
+ this.blocks.set(cid.toString(), bytes);
42
+ }
43
+ *entries() {
44
+ for (const [str, bytes] of this.blocks) {
45
+ yield { cid: parse(str), bytes };
46
+ }
47
+ }
48
+ }
49
+ export const blocksToCarBlock = async (rootCids, blocks) => {
50
+ // console.log('blocksToCarBlock', rootCids, blocks.constructor.name)
51
+ let size = 0;
52
+ if (!Array.isArray(rootCids)) {
53
+ rootCids = [rootCids];
54
+ }
55
+ const headerSize = CBW.headerLength({ roots: rootCids });
56
+ size += headerSize;
57
+ if (!Array.isArray(blocks)) {
58
+ blocks = Array.from(blocks.entries());
59
+ }
60
+ for (const { cid, bytes } of blocks) {
61
+ // console.log(cid, bytes)
62
+ size += CBW.blockLength({ cid, bytes });
63
+ }
64
+ const buffer = new Uint8Array(size);
65
+ const writer = await CBW.createWriter(buffer, { headerSize });
66
+ for (const cid of rootCids) {
67
+ writer.addRoot(cid);
68
+ }
69
+ for (const { cid, bytes } of blocks) {
70
+ writer.write({ cid, bytes });
71
+ }
72
+ await writer.close();
73
+ return await Block.encode({ value: writer.bytes, hasher: sha256, codec: raw });
74
+ };
75
+ export const blocksToEncryptedCarBlock = async (innerBlockStoreClockRootCid, blocks, keyMaterial, cids) => {
76
+ const encryptionKey = Buffer.from(keyMaterial, 'hex');
77
+ const encryptedBlocks = [];
78
+ const theCids = cids;
79
+ // console.trace('blocksToEncryptedCarBlock', blocks)
80
+ // for (const { cid } of blocks.entries()) {
81
+ // theCids.push(cid.toString())
82
+ // }
83
+ // console.log(
84
+ // 'encrypting',
85
+ // theCids.length,
86
+ // 'blocks',
87
+ // theCids.includes(innerBlockStoreClockRootCid.toString()),
88
+ // keyMaterial
89
+ // )
90
+ // console.log('cids', theCids, innerBlockStoreClockRootCid.toString())
91
+ let last;
92
+ for await (const block of encrypt({
93
+ cids: theCids,
94
+ get: async (cid) => {
95
+ // console.log('getencrypt', cid)
96
+ const got = blocks.get(cid);
97
+ // console.log('got', got)
98
+ return got.block ? ({ cid, bytes: got.block }) : got;
99
+ },
100
+ key: encryptionKey,
101
+ hasher: sha256,
102
+ chunker,
103
+ cache,
104
+ // codec: dagcbor, // should be crypto?
105
+ root: innerBlockStoreClockRootCid
106
+ })) {
107
+ encryptedBlocks.push(block);
108
+ last = block;
109
+ }
110
+ // console.log('last', last.cid.toString(), 'for clock', innerBlockStoreClockRootCid.toString())
111
+ const encryptedCar = await blocksToCarBlock(last.cid, encryptedBlocks);
112
+ return encryptedCar;
113
+ };
114
+ // { root, get, key, cache, chunker, hasher }
115
+ const memoizeDecryptedCarBlocks = new Map();
116
+ export const blocksFromEncryptedCarBlock = async (cid, get, keyMaterial) => {
117
+ if (memoizeDecryptedCarBlocks.has(cid.toString())) {
118
+ return memoizeDecryptedCarBlocks.get(cid.toString());
119
+ }
120
+ else {
121
+ const blocksPromise = (async () => {
122
+ const decryptionKey = Buffer.from(keyMaterial, 'hex');
123
+ // console.log('decrypting', keyMaterial, cid.toString())
124
+ const cids = new Set();
125
+ const decryptedBlocks = [];
126
+ for await (const block of decrypt({
127
+ root: cid,
128
+ get,
129
+ key: decryptionKey,
130
+ chunker,
131
+ hasher: sha256,
132
+ cache
133
+ // codec: dagcbor
134
+ })) {
135
+ // console.log('decrypted', block.cid.toString())
136
+ decryptedBlocks.push(block);
137
+ cids.add(block.cid.toString());
138
+ }
139
+ return { blocks: decryptedBlocks, cids };
140
+ })();
141
+ memoizeDecryptedCarBlocks.set(cid.toString(), blocksPromise);
142
+ return blocksPromise;
143
+ }
144
+ };
package/dist/sync.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import SimplePeer from 'simple-peer';
2
2
  import { parseCID } from './database.js';
3
3
  import { decodeEventBlock } from './clock.js';
4
- import { blocksToCarBlock, blocksToEncryptedCarBlock } from './storage/base.js';
4
+ import { blocksToCarBlock, blocksToEncryptedCarBlock } from './storage/utils.js';
5
5
  import { CarReader } from '@ipld/car';
6
6
  /**
7
7
  * @typedef {import('./database.js').Database} Database
package/dist/valet.js CHANGED
@@ -75,16 +75,22 @@ export class Valet {
75
75
  if (this.secondary) {
76
76
  // console.log('getValetBlock secondary', dataCID)
77
77
  try {
78
+ // eslint-disable-next-line
78
79
  const { block, reader } = await this.secondary.getLoaderBlock(dataCID);
80
+ const writeableCarReader = await this.primary.getWriteableCarReader(reader);
81
+ // console.log('getValetBlock secondary', dataCID, block.length)
82
+ // eslint-disable-next-line
79
83
  const cids = new Set();
80
84
  for await (const { cid } of reader.entries()) {
81
85
  // console.log(cid, bytes)
82
86
  cids.add(cid.toString());
83
87
  }
84
- reader.get = reader.gat; // some consumers prefer get
88
+ // reader.get = reader.gat // some consumers prefer get
85
89
  // console.log('replicating', reader.root)
86
- reader.lastCid = reader.root.cid;
87
- await this.primary.parkCar(reader, [...cids]);
90
+ writeableCarReader.lastCid = reader.root.cid;
91
+ writeableCarReader.head = [];
92
+ await this.primary.parkCar(writeableCarReader, cids).catch(e => console.error('parkCar error', e));
93
+ // console.log('FIX THIS', did)
88
94
  return block;
89
95
  }
90
96
  catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fireproof/core",
3
- "version": "0.7.3-dev.2",
3
+ "version": "0.8.0-dev.2",
4
4
  "description": "Live data for React, accelerated by proofs, powered by IPFS",
5
5
  "main": "dist/src/fireproof.js",
6
6
  "module": "dist/src/fireproof.mjs",
@@ -42,10 +42,8 @@
42
42
  "@ipld/car": "^5.1.0",
43
43
  "@ipld/dag-cbor": "^9.0.0",
44
44
  "@jsonlines/core": "^1.0.2",
45
- "@ucanto/principal": "^8.0.0",
46
- "@ucanto/server": "^8.0.1",
47
- "@ucanto/transport": "^8.0.0",
48
- "@ucanto/validator": "^8.0.0",
45
+ "@web3-storage/clock": "^0.3.0",
46
+ "@web3-storage/w3up-client": "^7.0.0",
49
47
  "async": "^3.2.4",
50
48
  "charwise": "^3.0.1",
51
49
  "cross-fetch": "^3.1.6",
package/src/blockstore.js CHANGED
@@ -221,6 +221,9 @@ export const doTransaction = async (label, blockstore, doFun, doSync = true) =>
221
221
  const innerBlockstore = blockstore.begin(label)
222
222
  try {
223
223
  const result = await doFun(innerBlockstore)
224
+ // console.log('doTransaction', label, 'result', result.head)
225
+ if (result && result.head) { innerBlockstore.head = result.head }
226
+ // pass the latest clock head for writing to the valet
224
227
  // @ts-ignore
225
228
  await blockstore.commit(innerBlockstore, doSync)
226
229
  return result
@@ -236,6 +239,7 @@ export const doTransaction = async (label, blockstore, doFun, doSync = true) =>
236
239
  export class InnerBlockstore {
237
240
  /** @type {Map<string, Uint8Array>} */
238
241
  blocks = new Map()
242
+ head = []
239
243
  lastCid = null
240
244
  label = ''
241
245
  parentBlockstore = null
package/src/crypto.js CHANGED
@@ -16,7 +16,11 @@ const encrypt = async function * ({ get, cids, hasher, key, cache, chunker, root
16
16
  let eroot
17
17
  for (const string of cids) {
18
18
  const cid = CID.parse(string)
19
- const unencrypted = await get(cid)
19
+ let unencrypted = await get(cid)
20
+ if (!unencrypted.cid) {
21
+ unencrypted = { cid, bytes: unencrypted }
22
+ }
23
+ // console.log('unencrypted', unencrypted)
20
24
  const block = await encode({ ...await codec.encrypt({ ...unencrypted, key }), codec, hasher })
21
25
  // console.log(`encrypting ${string} as ${block.cid}`)
22
26
  yield block
package/src/database.js CHANGED
@@ -5,6 +5,8 @@ import charwise from 'charwise'
5
5
  import { CID } from 'multiformats'
6
6
  import { DbIndex as Index } from './db-index.js'
7
7
 
8
+ import { Remote } from './remote.js'
9
+
8
10
  // TypeScript Types
9
11
  // eslint-disable-next-line no-unused-vars
10
12
  // import { CID } from 'multiformats/dist/types/src/cid.js'
@@ -29,14 +31,15 @@ export class Database {
29
31
  indexes = new Map()
30
32
  rootCache = null
31
33
  eventsCache = new Map()
32
-
34
+ remote = null
35
+ name = ''
33
36
  constructor (name, config = {}) {
34
37
  this.name = name
35
38
  this.clock = []
36
39
  this.instanceId = `fp.${this.name}.${Math.random().toString(36).substring(2, 7)}`
37
40
  this.blocks = new TransactionBlockstore(name, config)
38
41
  this.indexBlocks = new TransactionBlockstore(name ? name + '.indexes' : null, { primary: config.index })
39
-
42
+ this.remote = new Remote(this, name, config)
40
43
  this.config = config
41
44
  // todo we can wait for index blocks elsewhere
42
45
  this.ready = Promise.all([this.blocks.ready, this.indexBlocks.ready]).then(([blocksReady, indexReady]) => {
@@ -52,7 +55,7 @@ export class Database {
52
55
  clock.add(cid)
53
56
  }
54
57
  if (header.index) {
55
- this.indexBlocks.valet.primary.setCarCidMapCarCid(header.index.car)
58
+ this.indexBlocks.valet.primary.setLastCar(header.index.car)
56
59
  this.indexBlocks.valet.primary.setKeyMaterial(header.index.key)
57
60
  }
58
61
  if (header.indexes) {
@@ -92,11 +95,11 @@ export class Database {
92
95
 
93
96
  toHeader () {
94
97
  return {
95
- clock: this.clockToJSON(),
98
+ // clock: this.clockToJSON(),
96
99
  name: this.name,
97
100
  index: {
98
101
  key: this.indexBlocks.valet?.primary.keyMaterial,
99
- car: this.indexBlocks.valet?.primary.valetRootCarCid?.toString()
102
+ car: this.indexBlocks.valet?.primary.lastCar?.toString()
100
103
  },
101
104
  indexes: [...this.indexes.values()].map(index => index.toJSON())
102
105
  }
@@ -112,9 +115,9 @@ export class Database {
112
115
  return (clock || this.clock).map(cid => cid.toString())
113
116
  }
114
117
 
115
- maybeSaveClock () {
118
+ async maybeSaveClock () {
116
119
  if (this.name && this.blocks.valet) {
117
- this.blocks.valet.saveHeader(this.toHeader())
120
+ await this.blocks.valet.saveHeader(this.toHeader())
118
121
  }
119
122
  }
120
123
 
@@ -277,11 +280,13 @@ export class Database {
277
280
  * @memberof Fireproof
278
281
  * @instance
279
282
  */
280
- async put ({ _id, _proof, ...doc }) {
283
+ async put ({ _id, _proof, _clock, ...doc }) {
281
284
  await this.ready
282
285
  const id = _id || 'f' + Math.random().toString(36).slice(2)
286
+ doc = JSON.parse(JSON.stringify(doc))
287
+ if (_clock) doc._clock = _clock
283
288
  await this.runValidation({ _id: id, ...doc })
284
- return await this.putToProllyTree({ key: id, value: doc }, doc._clock)
289
+ return await this.putToProllyTree({ key: id, value: doc }, _clock)
285
290
  }
286
291
 
287
292
  /**
package/src/fireproof.js CHANGED
@@ -62,6 +62,7 @@ export class Fireproof {
62
62
 
63
63
  static snapshot (database, clock) {
64
64
  const definition = database.toJSON()
65
+ definition.clock = database.clockToJSON()
65
66
  if (clock) {
66
67
  definition.clock = clock.map(c => parseCID(c))
67
68
  definition.indexes.forEach(index => {
package/src/loader.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { Browser } from './storage/browser.js'
2
2
  import { Rest } from './storage/rest.js'
3
- import { UCAN } from './storage/ucan.js'
4
3
 
5
4
  export const Loader = {
6
5
  appropriate: (name, config = {}) => {
@@ -12,10 +11,6 @@ export const Loader = {
12
11
  return new Rest(name, config)
13
12
  }
14
13
 
15
- if (config.type === 'ucan') {
16
- return new UCAN(name, config)
17
- }
18
-
19
14
  return new Browser(name, config)
20
15
  }
21
16
  }
package/src/prolly.js CHANGED
@@ -307,7 +307,7 @@ export async function root (inBlocks, head, doFull = false) {
307
307
  bigPut(nb)
308
308
  }
309
309
  // console.log('root root', newProllyRootNode.constructor.name, newProllyRootNode)
310
- return { clockCIDs, node: newProllyRootNode }
310
+ return { clockCIDs, node: newProllyRootNode, head }
311
311
  },
312
312
  false
313
313
  )