hypercore 10.38.2 → 11.0.0

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.
@@ -0,0 +1,236 @@
1
+ const crypto = require('hypercore-crypto')
2
+ const flat = require('flat-tree')
3
+ const b4a = require('b4a')
4
+ const quickbit = require('quickbit-universal')
5
+ const Bitfield = require('./bitfield')
6
+
7
+ const MAX_BATCH_USED = 4 * 1024 * 1024
8
+ const MIN_BATCH_USED = 512 * 1024
9
+
10
+ // just in its own file as its a bit involved
11
+
12
+ module.exports = copyPrologue
13
+
14
+ async function copyPrologue (src, dst) {
15
+ const prologue = dst.header.manifest.prologue
16
+
17
+ if (src.tree.length < prologue.length || prologue.length === 0) return
18
+
19
+ const stack = []
20
+ const roots = flat.fullRoots(prologue.length * 2)
21
+ const batch = { roots, first: true, last: false, contig: 0, used: 0, tree: [], blocks: [] }
22
+
23
+ for (let i = 0; i < roots.length; i++) {
24
+ const node = roots[i]
25
+ batch.tree.push(node)
26
+ stack.push(node)
27
+ }
28
+
29
+ let lastPage = -1
30
+ let lastBlock = -1
31
+
32
+ for await (const data of src.storage.createBlockStream({ gte: 0, lt: prologue.length, reverse: true })) {
33
+ if (walkTree(stack, data.index * 2, batch) === false) {
34
+ throw new Error('Missing block or tree node for ' + data.index)
35
+ }
36
+
37
+ batch.contig = data.index + 1 === lastBlock ? batch.contig + 1 : 1
38
+ lastBlock = data.index
39
+
40
+ const page = getBitfieldPage(data.index)
41
+ batch.blocks.push(data)
42
+
43
+ if (lastPage !== page) batch.used += 4096
44
+ batch.used += Math.max(data.value.byteLength, 128) // 128 is just a sanity number to avoid mega batches
45
+
46
+ // always safe to partially flush so we do that ondemand to reduce memory usage...
47
+ if ((batch.used >= MIN_BATCH_USED && page !== lastPage) || (batch.used >= MAX_BATCH_USED)) {
48
+ await flushBatch(prologue, src, dst, batch)
49
+ }
50
+
51
+ lastPage = page
52
+ }
53
+
54
+ if (lastBlock !== 0) batch.contig = 0
55
+
56
+ batch.last = true
57
+ await flushBatch(prologue, src, dst, batch)
58
+ }
59
+
60
+ async function flushBatch (prologue, src, dst, batch) {
61
+ const nodePromises = []
62
+
63
+ const srcReader = src.storage.read()
64
+ for (const index of batch.tree) {
65
+ nodePromises.push(srcReader.getTreeNode(index))
66
+ }
67
+ srcReader.tryFlush()
68
+
69
+ const nodes = await Promise.all(nodePromises)
70
+
71
+ const pagePromises = []
72
+ const dstReader = dst.storage.read()
73
+
74
+ const headPromise = batch.first ? dstReader.getHead() : null
75
+ if (headPromise) headPromise.catch(noop)
76
+
77
+ let lastPage = -1
78
+ for (const { index } of batch.blocks) {
79
+ const page = getBitfieldPage(index)
80
+ if (page === lastPage) continue
81
+ lastPage = page
82
+ pagePromises.push(dstReader.getBitfieldPage(page))
83
+ }
84
+
85
+ dstReader.tryFlush()
86
+
87
+ const pages = await Promise.all(pagePromises)
88
+ const head = headPromise === null ? null : await headPromise
89
+ const userData = []
90
+
91
+ // reads done!
92
+
93
+ if (batch.first) {
94
+ const treeHash = crypto.tree(nodes.slice(0, batch.roots.length))
95
+ if (!b4a.equals(treeHash, prologue.hash)) throw new Error('Prologue does not match source')
96
+ }
97
+
98
+ if (batch.first) {
99
+ for await (const data of src.storage.createUserDataStream()) userData.push(data)
100
+ }
101
+
102
+ for (let i = 0; i < pages.length; i++) {
103
+ if (!pages[i]) pages[i] = b4a.alloc(4096)
104
+ }
105
+
106
+ const tx = dst.storage.write()
107
+
108
+ for (const node of nodes) tx.putTreeNode(node)
109
+
110
+ lastPage = -1
111
+ let pageIndex = -1
112
+
113
+ for (const { index, value } of batch.blocks) {
114
+ const page = getBitfieldPage(index)
115
+
116
+ if (page !== lastPage) {
117
+ lastPage = page
118
+ pageIndex++
119
+ // queue the page now, we mutate it below but its the same ref
120
+ tx.putBitfieldPage(pageIndex, pages[pageIndex])
121
+ }
122
+
123
+ const pageBuffer = pages[pageIndex]
124
+ quickbit.set(pageBuffer, getBitfieldOffset(index), true)
125
+ tx.putBlock(index, value)
126
+ }
127
+
128
+ for (const { key, value } of userData) {
129
+ tx.setUserData(key, value)
130
+ }
131
+
132
+ let upgraded = batch.first && !head
133
+ if (upgraded) {
134
+ tx.setHead(prologueToTree(prologue))
135
+ }
136
+
137
+ await tx.flush()
138
+
139
+ if (upgraded) {
140
+ const roots = nodes.slice(0, batch.roots.length)
141
+ dst.state.setRoots(roots)
142
+ dst.header.tree = prologueToTree(prologue)
143
+ }
144
+
145
+ if (userData.length > 0) {
146
+ dst.header.userData = userData.concat(dst.header.userData)
147
+ }
148
+
149
+ if (batch.contig) {
150
+ // TODO: we need to persist this somehow
151
+ dst.header.hints.contiguousLength = batch.contig
152
+ }
153
+
154
+ let start = 0
155
+ let length = 0
156
+
157
+ // update in memory bitfield
158
+ for (const { index } of batch.blocks) {
159
+ if (start === 0 || start - 1 === index) {
160
+ length++
161
+ } else {
162
+ if (length > 0) signalReplicator(dst, upgraded, start, length)
163
+ upgraded = false
164
+ length = 1
165
+ }
166
+
167
+ start = index
168
+ dst.bitfield.set(index, true)
169
+ }
170
+
171
+ if (length > 0) signalReplicator(dst, upgraded, start, length)
172
+
173
+ // unlink
174
+ batch.tree = []
175
+ batch.blocks = []
176
+ batch.first = false
177
+ batch.used = 0
178
+ }
179
+
180
+ function signalReplicator (core, upgraded, start, length) {
181
+ if (upgraded) {
182
+ core.replicator.cork()
183
+ core.replicator.onhave(start, length, false)
184
+ core.replicator.onupgrade()
185
+ core.replicator.uncork()
186
+ } else {
187
+ core.replicator.onhave(start, length, false)
188
+ }
189
+ }
190
+
191
+ function prologueToTree (prologue) {
192
+ return {
193
+ fork: 0,
194
+ length: prologue.length,
195
+ rootHash: prologue.hash,
196
+ signature: null
197
+ }
198
+ }
199
+
200
+ function getBitfieldPage (index) {
201
+ return Math.floor(index / Bitfield.BITS_PER_PAGE)
202
+ }
203
+
204
+ function getBitfieldOffset (index) {
205
+ return index & (Bitfield.BITS_PER_PAGE - 1)
206
+ }
207
+
208
+ function walkTree (stack, target, batch) {
209
+ while (stack.length > 0) {
210
+ const node = stack.pop()
211
+
212
+ if ((node & 1) === 0) {
213
+ if (node === target) return true
214
+ continue
215
+ }
216
+
217
+ const ite = flat.iterator(node)
218
+ if (!ite.contains(target)) continue
219
+
220
+ while ((ite.index & 1) !== 0) {
221
+ const left = ite.leftChild()
222
+ const right = ite.sibling() // is right child
223
+
224
+ batch.tree.push(left, right)
225
+
226
+ if (ite.contains(target)) stack.push(left)
227
+ else ite.sibling()
228
+ }
229
+
230
+ if (ite.index === target) return true
231
+ }
232
+
233
+ return false
234
+ }
235
+
236
+ function noop () {}