+812
PDSharp.Core/Mst.fs
+812
PDSharp.Core/Mst.fs
···
1
+
namespace PDSharp.Core
2
+
3
+
open System
4
+
open System.Collections.Generic
5
+
open System.Formats.Cbor
6
+
7
+
module Mst =
8
+
type MstEntry = {
9
+
PrefixLen : int
10
+
KeySuffix : string
11
+
Value : Cid
12
+
Tree : Cid option
13
+
}
14
+
15
+
type MstNode = { Left : Cid option; Entries : MstEntry list }
16
+
17
+
/// Layer Calculation
18
+
let layer (key : string) : int =
19
+
let hash = Crypto.sha256Str key
20
+
let mutable zeros = 0
21
+
let mutable i = 0
22
+
let mutable found = false
23
+
24
+
while i < hash.Length && not found do
25
+
let b = hash.[i]
26
+
27
+
if b = 0uy then
28
+
zeros <- zeros + 8
29
+
i <- i + 1
30
+
else
31
+
let mutable mask = 0x80uy
32
+
33
+
while b &&& mask = 0uy do
34
+
zeros <- zeros + 1
35
+
mask <- mask >>> 1
36
+
37
+
found <- true
38
+
39
+
zeros / 2
40
+
41
+
let entryToCborObj (e : MstEntry) : obj =
42
+
let arr = Array.zeroCreate<obj> 4
43
+
arr.[0] <- e.PrefixLen :> obj
44
+
arr.[1] <- e.KeySuffix :> obj
45
+
arr.[2] <- e.Value :> obj
46
+
47
+
arr.[3] <-
48
+
match e.Tree with
49
+
| Some c -> c :> obj
50
+
| None -> null
51
+
52
+
arr :> obj
53
+
54
+
let nodeToCborObj (node : MstNode) : obj =
55
+
let arr = Array.zeroCreate<obj> 2
56
+
57
+
arr.[0] <-
58
+
match node.Left with
59
+
| Some c -> c :> obj
60
+
| None -> null
61
+
62
+
let entriesArr = node.Entries |> List.map entryToCborObj |> Seq.toArray
63
+
arr.[1] <- entriesArr :> obj
64
+
arr :> obj
65
+
66
+
let serialize (node : MstNode) : byte[] = nodeToCborObj node |> DagCbor.encode
67
+
68
+
let readCid (reader : CborReader) =
69
+
let tag = reader.ReadTag()
70
+
let tag42 = LanguagePrimitives.EnumOfValue<uint64, CborTag>(42UL)
71
+
72
+
if tag <> tag42 then
73
+
failwith "Expected CID tag 42"
74
+
75
+
let bytes = reader.ReadByteString()
76
+
77
+
if bytes.Length > 0 && bytes.[0] = 0x00uy then
78
+
let raw = Array.zeroCreate<byte> (bytes.Length - 1)
79
+
Array.Copy(bytes, 1, raw, 0, raw.Length)
80
+
Cid raw
81
+
else
82
+
Cid bytes
83
+
84
+
let deserialize (data : byte[]) : MstNode =
85
+
let reader = new CborReader(data.AsMemory(), CborConformanceMode.Strict)
86
+
let len = reader.ReadStartArray()
87
+
88
+
if len.HasValue && len.Value <> 2 then
89
+
failwith "MST node must be array of length 2"
90
+
91
+
let left =
92
+
match reader.PeekState() with
93
+
| CborReaderState.Null ->
94
+
reader.ReadNull()
95
+
None
96
+
| _ -> Some(readCid reader)
97
+
98
+
let entriesLen = reader.ReadStartArray()
99
+
let mutable entries = []
100
+
101
+
let count =
102
+
if entriesLen.HasValue then
103
+
entriesLen.Value
104
+
else
105
+
Int32.MaxValue
106
+
107
+
let mutable i = 0
108
+
109
+
while i < count && reader.PeekState() <> CborReaderState.EndArray do
110
+
let entryLen = reader.ReadStartArray()
111
+
112
+
if entryLen.HasValue && entryLen.Value <> 4 then
113
+
failwith "MST entry must be array of length 4"
114
+
115
+
let p = reader.ReadInt32()
116
+
let k = reader.ReadTextString()
117
+
let v = readCid reader
118
+
119
+
let t =
120
+
match reader.PeekState() with
121
+
| CborReaderState.Null ->
122
+
reader.ReadNull()
123
+
None
124
+
| _ -> Some(readCid reader)
125
+
126
+
reader.ReadEndArray()
127
+
128
+
entries <- entries @ [ { PrefixLen = p; KeySuffix = k; Value = v; Tree = t } ]
129
+
i <- i + 1
130
+
131
+
reader.ReadEndArray()
132
+
reader.ReadEndArray()
133
+
{ Left = left; Entries = entries }
134
+
135
+
let compareKeys (a : string) (b : string) : int =
136
+
let bytesA = System.Text.Encoding.UTF8.GetBytes(a)
137
+
let bytesB = System.Text.Encoding.UTF8.GetBytes(b)
138
+
let len = min bytesA.Length bytesB.Length
139
+
let mutable res = 0
140
+
let mutable i = 0
141
+
142
+
while res = 0 && i < len do
143
+
res <- bytesA.[i].CompareTo(bytesB.[i])
144
+
i <- i + 1
145
+
146
+
if res <> 0 then
147
+
res
148
+
else
149
+
bytesA.Length.CompareTo bytesB.Length
150
+
151
+
type NodeLoader = Cid -> Async<MstNode option>
152
+
type NodePersister = MstNode -> Async<Cid>
153
+
154
+
let storeNode (persister : NodePersister) (node : MstNode) : Async<Cid> = persister node
155
+
156
+
let rec get (loader : NodeLoader) (node : MstNode) (key : string) (prevKey : string) : Async<Cid option> = async {
157
+
let mutable currentKey = prevKey
158
+
let mutable foundEntry : MstEntry option = None
159
+
let mutable nextTree : Cid option = node.Left
160
+
let mutable stop = false
161
+
let mutable i = 0
162
+
163
+
while not stop && i < node.Entries.Length do
164
+
let e = node.Entries.[i]
165
+
166
+
let prefix =
167
+
if e.PrefixLen > currentKey.Length then
168
+
currentKey
169
+
else
170
+
currentKey.Substring(0, e.PrefixLen)
171
+
172
+
let fullKey = prefix + e.KeySuffix
173
+
let cmp = compareKeys key fullKey
174
+
175
+
if cmp = 0 then
176
+
foundEntry <- Some e
177
+
stop <- true
178
+
elif cmp < 0 then
179
+
stop <- true
180
+
else
181
+
nextTree <- e.Tree
182
+
currentKey <- fullKey
183
+
i <- i + 1
184
+
185
+
match foundEntry with
186
+
| Some e -> return Some e.Value
187
+
| None ->
188
+
match nextTree with
189
+
| None -> return None
190
+
| Some cid ->
191
+
let! child = loader cid
192
+
193
+
match child with
194
+
| Some cNode -> return! get loader cNode key currentKey
195
+
| None -> return None
196
+
}
197
+
198
+
let sharedPrefixLen (a : string) (b : string) =
199
+
let len = min a.Length b.Length
200
+
let mutable i = 0
201
+
202
+
while i < len && a.[i] = b.[i] do
203
+
i <- i + 1
204
+
205
+
i
206
+
207
+
let nodeLayer (node : MstNode) (prevKey : string) =
208
+
match node.Entries with
209
+
| [] -> -1
210
+
| first :: _ ->
211
+
let prefix =
212
+
if first.PrefixLen > prevKey.Length then
213
+
prevKey
214
+
else
215
+
prevKey.Substring(0, first.PrefixLen)
216
+
217
+
let fullKey = prefix + first.KeySuffix
218
+
layer fullKey
219
+
220
+
/// Splits a node around a key (which is assumed to be higher layer than any in the node).
221
+
///
222
+
/// Returns (LeftNode, RightNode).
223
+
let rec split
224
+
(loader : NodeLoader)
225
+
(persister : NodePersister)
226
+
(node : MstNode)
227
+
(key : string)
228
+
(prevKey : string)
229
+
: Async<MstNode * MstNode> =
230
+
async {
231
+
let mutable splitIdx = -1
232
+
let mutable found = false
233
+
let mutable currentKey = prevKey
234
+
let mutable i = 0
235
+
let entries = node.Entries
236
+
let mutable splitPrevKey = prevKey
237
+
238
+
while i < entries.Length && not found do
239
+
let e = entries.[i]
240
+
241
+
let prefix =
242
+
if e.PrefixLen > currentKey.Length then
243
+
currentKey
244
+
else
245
+
currentKey.Substring(0, e.PrefixLen)
246
+
247
+
let fullKey = prefix + e.KeySuffix
248
+
249
+
if compareKeys key fullKey < 0 then
250
+
splitIdx <- i
251
+
found <- true
252
+
else
253
+
currentKey <- fullKey
254
+
splitPrevKey <- currentKey
255
+
i <- i + 1
256
+
257
+
let childCidToSplit =
258
+
if found then
259
+
if splitIdx = 0 then
260
+
node.Left
261
+
else
262
+
entries.[splitIdx - 1].Tree
263
+
else if entries.Length = 0 then
264
+
node.Left
265
+
else
266
+
entries.[entries.Length - 1].Tree
267
+
268
+
let! (lChild, rChild) = async {
269
+
match childCidToSplit with
270
+
| None -> return ({ Left = None; Entries = [] }, { Left = None; Entries = [] })
271
+
| Some cid ->
272
+
let! childNodeOpt = loader cid
273
+
274
+
match childNodeOpt with
275
+
| None -> return ({ Left = None; Entries = [] }, { Left = None; Entries = [] })
276
+
| Some childNode -> return! split loader persister childNode key currentKey
277
+
}
278
+
279
+
let persistOrNone (n : MstNode) = async {
280
+
if n.Entries.IsEmpty && n.Left.IsNone then
281
+
return None
282
+
else
283
+
let! c = persister n
284
+
return Some c
285
+
}
286
+
287
+
let! lCid = persistOrNone lChild
288
+
let! rCid = persistOrNone rChild
289
+
let leftEntries = if found then entries |> List.take splitIdx else entries
290
+
291
+
let newLeftEntries =
292
+
if leftEntries.IsEmpty then
293
+
[]
294
+
else
295
+
let last = leftEntries.[leftEntries.Length - 1]
296
+
let newLast = { last with Tree = lCid }
297
+
(leftEntries |> List.take (leftEntries.Length - 1)) @ [ newLast ]
298
+
299
+
let leftNode =
300
+
if leftEntries.IsEmpty then
301
+
{ Left = lCid; Entries = [] }
302
+
else
303
+
{ Left = node.Left; Entries = newLeftEntries }
304
+
305
+
let rightEntries = if found then entries |> List.skip splitIdx else []
306
+
307
+
let newRightEntries =
308
+
match rightEntries with
309
+
| [] -> []
310
+
| first :: rest ->
311
+
let firstFullKey =
312
+
let prefix =
313
+
if first.PrefixLen > currentKey.Length then
314
+
currentKey
315
+
else
316
+
currentKey.Substring(0, first.PrefixLen)
317
+
318
+
prefix + first.KeySuffix
319
+
320
+
let newP = sharedPrefixLen key firstFullKey
321
+
let newSuffix = firstFullKey.Substring(newP)
322
+
let newFirst = { first with PrefixLen = newP; KeySuffix = newSuffix }
323
+
newFirst :: rest
324
+
325
+
let rightNode = { Left = rCid; Entries = newRightEntries }
326
+
return (leftNode, rightNode)
327
+
}
328
+
329
+
let rec put
330
+
(loader : NodeLoader)
331
+
(persister : NodePersister)
332
+
(node : MstNode)
333
+
(key : string)
334
+
(value : Cid)
335
+
(prevKey : string)
336
+
: Async<MstNode> =
337
+
async {
338
+
let kLayer = layer key
339
+
340
+
let nLayer =
341
+
match node.Entries with
342
+
| [] -> -1
343
+
| first :: _ ->
344
+
let prefix =
345
+
if first.PrefixLen > prevKey.Length then
346
+
prevKey
347
+
else
348
+
prevKey.Substring(0, first.PrefixLen)
349
+
350
+
let fullKey = prefix + first.KeySuffix
351
+
layer fullKey
352
+
353
+
if kLayer > nLayer then
354
+
let! (lNode, rNode) = split loader persister node key prevKey
355
+
356
+
let persistOrNone (n : MstNode) = async {
357
+
if n.Entries.IsEmpty && n.Left.IsNone then
358
+
return None
359
+
else
360
+
let! c = persister n
361
+
return Some c
362
+
}
363
+
364
+
let! lCid = persistOrNone lNode
365
+
let! rCid = persistOrNone rNode
366
+
367
+
let p = sharedPrefixLen prevKey key
368
+
let suffix = key.Substring(p)
369
+
370
+
return {
371
+
Left = lCid
372
+
Entries = [
373
+
{
374
+
PrefixLen = p
375
+
KeySuffix = suffix
376
+
Value = value
377
+
Tree = rCid
378
+
}
379
+
]
380
+
}
381
+
382
+
elif kLayer < nLayer then
383
+
let mutable nextCid = node.Left
384
+
let mutable currentKey = prevKey
385
+
let mutable found = false
386
+
let mutable i = 0
387
+
388
+
let entries = node.Entries
389
+
let mutable childIdx = -1
390
+
391
+
while i < entries.Length && not found do
392
+
let e = entries.[i]
393
+
394
+
let prefix =
395
+
if e.PrefixLen > currentKey.Length then
396
+
currentKey
397
+
else
398
+
currentKey.Substring(0, e.PrefixLen)
399
+
400
+
let fullKey = prefix + e.KeySuffix
401
+
402
+
if compareKeys key fullKey < 0 then
403
+
found <- true
404
+
else
405
+
childIdx <- i
406
+
nextCid <- e.Tree
407
+
currentKey <- fullKey
408
+
i <- i + 1
409
+
410
+
let! childNode =
411
+
match nextCid with
412
+
| Some c -> loader c
413
+
| None -> async { return Some { Left = None; Entries = [] } }
414
+
415
+
match childNode with
416
+
| None -> return failwith "Failed to load child node"
417
+
| Some cn ->
418
+
let! newChildNode = put loader persister cn key value currentKey
419
+
let! newChildCid = persister newChildNode
420
+
421
+
if childIdx = -1 then
422
+
return { node with Left = Some newChildCid }
423
+
else
424
+
return {
425
+
node with
426
+
Entries =
427
+
entries
428
+
|> List.mapi (fun idx x ->
429
+
if idx = childIdx then
430
+
{ entries.[childIdx] with Tree = Some newChildCid }
431
+
else
432
+
x)
433
+
}
434
+
435
+
else
436
+
let mutable insertIdx = -1
437
+
let mutable found = false
438
+
let mutable currentKey = prevKey
439
+
let mutable i = 0
440
+
let mutable targetChildCid = None
441
+
let mutable childPrevKey = prevKey
442
+
443
+
let fullKeysCache = new List<string>()
444
+
445
+
while i < node.Entries.Length && not found do
446
+
let e = node.Entries.[i]
447
+
448
+
let prefix =
449
+
if e.PrefixLen > currentKey.Length then
450
+
currentKey
451
+
else
452
+
currentKey.Substring(0, e.PrefixLen)
453
+
454
+
let fullKey = prefix + e.KeySuffix
455
+
fullKeysCache.Add(fullKey)
456
+
457
+
let cmp = compareKeys key fullKey
458
+
459
+
if cmp = 0 then
460
+
insertIdx <- i
461
+
found <- true
462
+
elif cmp < 0 then
463
+
insertIdx <- i
464
+
targetChildCid <- if i = 0 then node.Left else node.Entries.[i - 1].Tree
465
+
childPrevKey <- currentKey
466
+
found <- true
467
+
else
468
+
currentKey <- fullKey
469
+
i <- i + 1
470
+
471
+
if not found then
472
+
insertIdx <- node.Entries.Length
473
+
474
+
targetChildCid <-
475
+
if node.Entries.Length = 0 then
476
+
node.Left
477
+
else
478
+
node.Entries.[node.Entries.Length - 1].Tree
479
+
480
+
childPrevKey <- currentKey
481
+
482
+
let entries = node.Entries
483
+
484
+
if found && compareKeys key fullKeysCache.[insertIdx] = 0 then
485
+
let e = entries.[insertIdx]
486
+
let newE = { e with Value = value }
487
+
488
+
let newEntries =
489
+
entries |> List.mapi (fun idx x -> if idx = insertIdx then newE else x)
490
+
491
+
return { node with Entries = newEntries }
492
+
else
493
+
let! (lChild, rChild) = async {
494
+
match targetChildCid with
495
+
| None -> return { Left = None; Entries = [] }, { Left = None; Entries = [] }
496
+
| Some cid ->
497
+
let! cOpt = loader cid
498
+
499
+
match cOpt with
500
+
| None -> return { Left = None; Entries = [] }, { Left = None; Entries = [] }
501
+
| Some c -> return! split loader persister c key childPrevKey
502
+
}
503
+
504
+
let persistOrNone (n : MstNode) = async {
505
+
if n.Entries.IsEmpty && n.Left.IsNone then
506
+
return None
507
+
else
508
+
let! c = persister n
509
+
return Some c
510
+
}
511
+
512
+
let! lCid = persistOrNone lChild
513
+
let! rCid = persistOrNone rChild
514
+
515
+
let beforeEntries =
516
+
if insertIdx = 0 then
517
+
[]
518
+
else
519
+
let prevE = entries.[insertIdx - 1]
520
+
let newPrevE = { prevE with Tree = lCid }
521
+
(entries |> List.take (insertIdx - 1)) @ [ newPrevE ]
522
+
523
+
let newLeft = if insertIdx = 0 then lCid else node.Left
524
+
let getFullKey idx = fullKeysCache.[idx]
525
+
let p = sharedPrefixLen childPrevKey key
526
+
let suffix = key.Substring(p)
527
+
528
+
let newEntry = {
529
+
PrefixLen = p
530
+
KeySuffix = suffix
531
+
Value = value
532
+
Tree = rCid
533
+
}
534
+
535
+
let afterEntries =
536
+
if insertIdx >= entries.Length then
537
+
[]
538
+
else
539
+
let first = entries.[insertIdx]
540
+
let firstFullKey = getFullKey insertIdx
541
+
542
+
let newP = sharedPrefixLen key firstFullKey
543
+
let newS = firstFullKey.Substring(newP)
544
+
let newFirst = { first with PrefixLen = newP; KeySuffix = newS }
545
+
546
+
[ newFirst ] @ (entries |> List.skip (insertIdx + 1))
547
+
548
+
let newEntries = beforeEntries @ [ newEntry ] @ afterEntries
549
+
550
+
return { Left = newLeft; Entries = newEntries }
551
+
}
552
+
553
+
// --- Merge Operation ---
554
+
let rec merge
555
+
(loader : NodeLoader)
556
+
(persister : NodePersister)
557
+
(leftCid : Cid option)
558
+
(rightCid : Cid option)
559
+
(prevKey : string)
560
+
: Async<MstNode> =
561
+
async {
562
+
match leftCid, rightCid with
563
+
| None, None -> return { Left = None; Entries = [] }
564
+
| Some l, None ->
565
+
let! n = loader l
566
+
return n |> Option.defaultValue { Left = None; Entries = [] }
567
+
| None, Some r ->
568
+
let! n = loader r
569
+
return n |> Option.defaultValue { Left = None; Entries = [] }
570
+
| Some l, Some r ->
571
+
let! lNodeOpt = loader l
572
+
let! rNodeOpt = loader r
573
+
let lNode = lNodeOpt |> Option.defaultValue { Left = None; Entries = [] }
574
+
let rNode = rNodeOpt |> Option.defaultValue { Left = None; Entries = [] }
575
+
576
+
let lLayer = nodeLayer lNode prevKey
577
+
let mutable current = prevKey
578
+
579
+
for e in lNode.Entries do
580
+
let p =
581
+
if e.PrefixLen > current.Length then
582
+
current
583
+
else
584
+
current.Substring(0, e.PrefixLen)
585
+
586
+
current <- p + e.KeySuffix
587
+
588
+
let rightPrevKey = current
589
+
590
+
let realLLayer = nodeLayer lNode prevKey
591
+
let realRLayer = nodeLayer rNode rightPrevKey
592
+
593
+
if realLLayer > realRLayer then
594
+
match lNode.Entries with
595
+
| [] -> return! merge loader persister lNode.Left rightCid prevKey
596
+
| entries ->
597
+
let lastIdx = entries.Length - 1
598
+
let lastEntry = entries.[lastIdx]
599
+
600
+
let! mergedChild = merge loader persister lastEntry.Tree rightCid rightPrevKey
601
+
let! mergedCid = persister mergedChild
602
+
603
+
let newEntry = { lastEntry with Tree = Some mergedCid }
604
+
let newEntries = (entries |> List.take lastIdx) @ [ newEntry ]
605
+
return { lNode with Entries = newEntries }
606
+
607
+
elif realRLayer > realLLayer then
608
+
match rNode.Entries with
609
+
| [] -> return! merge loader persister leftCid rNode.Left prevKey
610
+
| _ ->
611
+
let! mergedChild = merge loader persister leftCid rNode.Left prevKey
612
+
let! mergedCid = persister mergedChild
613
+
614
+
return { rNode with Left = Some mergedCid }
615
+
616
+
else
617
+
let boundaryL =
618
+
match lNode.Entries with
619
+
| [] -> lNode.Left
620
+
| es -> es.[es.Length - 1].Tree
621
+
622
+
let boundaryR = rNode.Left
623
+
624
+
let! mergedBoundaryNode = merge loader persister boundaryL boundaryR rightPrevKey
625
+
let! mergedBoundaryCid = persister mergedBoundaryNode
626
+
627
+
let newEntries =
628
+
match lNode.Entries with
629
+
| [] -> rNode.Entries
630
+
| lEntries ->
631
+
let lastIdx = lEntries.Length - 1
632
+
let lastE = lEntries.[lastIdx]
633
+
let newLastE = { lastE with Tree = Some mergedBoundaryCid }
634
+
(lEntries |> List.take lastIdx) @ [ newLastE ] @ rNode.Entries
635
+
636
+
let newLeft =
637
+
if lNode.Entries.IsEmpty then
638
+
Some mergedBoundaryCid
639
+
else
640
+
lNode.Left
641
+
642
+
return { Left = newLeft; Entries = newEntries }
643
+
}
644
+
645
+
let rec delete
646
+
(loader : NodeLoader)
647
+
(persister : NodePersister)
648
+
(node : MstNode)
649
+
(key : string)
650
+
(prevKey : string)
651
+
: Async<MstNode option> =
652
+
async {
653
+
let mutable currentKey = prevKey
654
+
let mutable foundIdx = -1
655
+
let mutable nextTreeIdx = -1
656
+
let mutable i = 0
657
+
let mutable found = false
658
+
659
+
while i < node.Entries.Length && not found do
660
+
let e = node.Entries.[i]
661
+
662
+
let prefix =
663
+
if e.PrefixLen > currentKey.Length then
664
+
currentKey
665
+
else
666
+
currentKey.Substring(0, e.PrefixLen)
667
+
668
+
let fullKey = prefix + e.KeySuffix
669
+
670
+
let cmp = compareKeys key fullKey
671
+
672
+
if cmp = 0 then
673
+
foundIdx <- i
674
+
found <- true
675
+
elif cmp < 0 then
676
+
found <- true
677
+
nextTreeIdx <- i - 1
678
+
else
679
+
currentKey <- fullKey
680
+
i <- i + 1
681
+
682
+
if not found then
683
+
nextTreeIdx <- node.Entries.Length - 1
684
+
685
+
if foundIdx <> -1 then
686
+
let e = node.Entries.[foundIdx]
687
+
688
+
let leftChildCid =
689
+
if foundIdx = 0 then
690
+
node.Left
691
+
else
692
+
node.Entries.[foundIdx - 1].Tree
693
+
694
+
let rightChildCid = e.Tree
695
+
696
+
let mergePrevKey = if foundIdx = 0 then prevKey else currentKey
697
+
698
+
let! mergedChildNode = merge loader persister leftChildCid rightChildCid mergePrevKey
699
+
let! mergedChildCid = persister mergedChildNode
700
+
701
+
let newEntries =
702
+
if foundIdx = 0 then
703
+
let rest = node.Entries |> List.skip 1
704
+
705
+
match rest with
706
+
| [] -> []
707
+
| first :: rs ->
708
+
let firstFullKey =
709
+
let oldP =
710
+
if first.PrefixLen > key.Length then
711
+
key
712
+
else
713
+
key.Substring(0, first.PrefixLen)
714
+
715
+
oldP + first.KeySuffix
716
+
717
+
let newP = sharedPrefixLen prevKey firstFullKey
718
+
let newSuffix = firstFullKey.Substring(newP)
719
+
let newFirst = { first with PrefixLen = newP; KeySuffix = newSuffix }
720
+
newFirst :: rs
721
+
else
722
+
let before = node.Entries |> List.take (foundIdx - 1)
723
+
let prevE = node.Entries.[foundIdx - 1]
724
+
let newPrevE = { prevE with Tree = Some mergedChildCid }
725
+
726
+
let rest = node.Entries |> List.skip (foundIdx + 1)
727
+
728
+
let newRest =
729
+
match rest with
730
+
| [] -> []
731
+
| first :: rs ->
732
+
let firstFullKey =
733
+
let oldP =
734
+
if first.PrefixLen > key.Length then
735
+
key
736
+
else
737
+
key.Substring(0, first.PrefixLen)
738
+
739
+
oldP + first.KeySuffix
740
+
741
+
let newP = sharedPrefixLen mergePrevKey firstFullKey
742
+
let newSuffix = firstFullKey.Substring(newP)
743
+
let newFirst = { first with PrefixLen = newP; KeySuffix = newSuffix }
744
+
newFirst :: rs
745
+
746
+
before @ [ newPrevE ] @ newRest
747
+
748
+
let newLeft = if foundIdx = 0 then Some mergedChildCid else node.Left
749
+
750
+
let newNode = { Left = newLeft; Entries = newEntries }
751
+
752
+
if newNode.Entries.IsEmpty && newNode.Left.IsNone then
753
+
return None
754
+
else
755
+
return Some newNode
756
+
757
+
else
758
+
let childCid =
759
+
if nextTreeIdx = -1 then
760
+
node.Left
761
+
else
762
+
node.Entries.[nextTreeIdx].Tree
763
+
764
+
match childCid with
765
+
| None -> return Some node
766
+
| Some cid ->
767
+
let! childNodeOpt = loader cid
768
+
769
+
match childNodeOpt with
770
+
| None -> return Some node
771
+
| Some childNode ->
772
+
let! newChildOpt = delete loader persister childNode key currentKey
773
+
774
+
match newChildOpt with
775
+
| None ->
776
+
if nextTreeIdx = -1 then
777
+
return Some { node with Left = None }
778
+
else
779
+
let prevE = node.Entries.[nextTreeIdx]
780
+
let newPrevE = { prevE with Tree = None }
781
+
782
+
let newEntries =
783
+
node.Entries
784
+
|> List.mapi (fun idx x -> if idx = nextTreeIdx then newPrevE else x)
785
+
786
+
return Some { node with Entries = newEntries }
787
+
| Some newChild ->
788
+
let! newChildCid = persister newChild
789
+
790
+
if nextTreeIdx = -1 then
791
+
return Some { node with Left = Some newChildCid }
792
+
else
793
+
let prevE = node.Entries.[nextTreeIdx]
794
+
let newPrevE = { prevE with Tree = Some newChildCid }
795
+
796
+
let newEntries =
797
+
node.Entries
798
+
|> List.mapi (fun idx x -> if idx = nextTreeIdx then newPrevE else x)
799
+
800
+
return Some { node with Entries = newEntries }
801
+
}
802
+
803
+
let fromEntries (loader : NodeLoader) (persister : NodePersister) (entries : (string * Cid) list) : Async<MstNode> = async {
804
+
let mutable root = { Left = None; Entries = [] }
805
+
let sortedEntries = entries |> List.sortBy fst
806
+
807
+
for k, v in sortedEntries do
808
+
let! newRoot = put loader persister root k v ""
809
+
root <- newRoot
810
+
811
+
return root
812
+
}
+1
PDSharp.Core/PDSharp.Core.fsproj
+1
PDSharp.Core/PDSharp.Core.fsproj
+264
PDSharp.Tests/Mst.Tests.fs
+264
PDSharp.Tests/Mst.Tests.fs
···
1
+
module MstTests
2
+
3
+
open Xunit
4
+
open PDSharp.Core
5
+
open PDSharp.Core.Mst
6
+
7
+
[<Fact>]
8
+
let ``Serialization Roundtrip`` () =
9
+
let cid1 = Cid(Crypto.sha256Str "val1")
10
+
11
+
let e1 = {
12
+
PrefixLen = 0
13
+
KeySuffix = "apple"
14
+
Value = cid1
15
+
Tree = None
16
+
}
17
+
18
+
let e2 = {
19
+
PrefixLen = 2
20
+
KeySuffix = "ricot"
21
+
Value = cid1
22
+
Tree = None
23
+
}
24
+
25
+
let node = { Left = None; Entries = [ e1; e2 ] }
26
+
27
+
let bytes = Mst.serialize node
28
+
let node2 = Mst.deserialize bytes
29
+
30
+
Assert.Equal(node.Entries.Length, node2.Entries.Length)
31
+
Assert.Equal("apple", node2.Entries.[0].KeySuffix)
32
+
Assert.Equal("ricot", node2.Entries.[1].KeySuffix)
33
+
Assert.Equal(2, node2.Entries.[1].PrefixLen)
34
+
35
+
[<Fact>]
36
+
let ``Get Operation Linear Scan`` () =
37
+
let cid1 = Cid(Crypto.sha256Str "val1")
38
+
let cid2 = Cid(Crypto.sha256Str "val2")
39
+
40
+
let e1 = {
41
+
PrefixLen = 0
42
+
KeySuffix = "apple"
43
+
Value = cid1
44
+
Tree = None
45
+
}
46
+
47
+
let e2 = {
48
+
PrefixLen = 0
49
+
KeySuffix = "banana"
50
+
Value = cid2
51
+
Tree = None
52
+
}
53
+
54
+
let node = { Left = None; Entries = [ e1; e2 ] }
55
+
56
+
let loader (c : Cid) = async { return None }
57
+
58
+
let res1 = Mst.get loader node "apple" "" |> Async.RunSynchronously
59
+
Assert.Equal(Some cid1, res1)
60
+
61
+
let res2 = Mst.get loader node "banana" "" |> Async.RunSynchronously
62
+
Assert.Equal(Some cid2, res2)
63
+
64
+
let res3 = Mst.get loader node "cherry" "" |> Async.RunSynchronously
65
+
Assert.True(Option.isNone res3)
66
+
67
+
[<Fact>]
68
+
let ``Get Operation With Prefix Compression`` () =
69
+
let cid1 = Cid(Crypto.sha256Str "val1")
70
+
let cid2 = Cid(Crypto.sha256Str "val2")
71
+
72
+
let e1 = {
73
+
PrefixLen = 0
74
+
KeySuffix = "apple"
75
+
Value = cid1
76
+
Tree = None
77
+
}
78
+
79
+
let e2 = {
80
+
PrefixLen = 2
81
+
KeySuffix = "ricot"
82
+
Value = cid2
83
+
Tree = None
84
+
}
85
+
86
+
let node = { Left = None; Entries = [ e1; e2 ] }
87
+
let loader (c : Cid) = async { return None }
88
+
89
+
let res1 = Mst.get loader node "apricot" "" |> Async.RunSynchronously
90
+
Assert.Equal(Some cid2, res1)
91
+
92
+
[<Fact>]
93
+
let ``Put Operation Simple Insert`` () =
94
+
let store = System.Collections.Concurrent.ConcurrentDictionary<string, MstNode>()
95
+
96
+
let loader (c : Cid) = async {
97
+
let key = System.Convert.ToBase64String(c.Bytes)
98
+
let success, node = store.TryGetValue(key)
99
+
return if success then Some node else None
100
+
}
101
+
102
+
let persister (n : MstNode) = async {
103
+
let bytes = Mst.serialize n
104
+
let cid = Cid(Crypto.sha256 bytes)
105
+
let key = System.Convert.ToBase64String(cid.Bytes)
106
+
store.[key] <- n
107
+
return cid
108
+
}
109
+
110
+
let node = { Left = None; Entries = [] }
111
+
let cid1 = Cid(Crypto.sha256Str "v1")
112
+
let node2 = Mst.put loader persister node "apple" cid1 "" |> Async.RunSynchronously
113
+
114
+
Assert.Equal(1, node2.Entries.Length)
115
+
Assert.Equal("apple", node2.Entries.[0].KeySuffix)
116
+
Assert.Equal(0, node2.Entries.[0].PrefixLen)
117
+
Assert.Equal(cid1, node2.Entries.[0].Value)
118
+
119
+
let res = Mst.get loader node2 "apple" "" |> Async.RunSynchronously
120
+
Assert.Equal(Some cid1, res)
121
+
122
+
[<Fact>]
123
+
let ``Put Operation Multiple Sorted`` () =
124
+
let store = System.Collections.Concurrent.ConcurrentDictionary<string, MstNode>()
125
+
126
+
let loader (c : Cid) = async {
127
+
let key = System.Convert.ToBase64String(c.Bytes)
128
+
let success, node = store.TryGetValue(key)
129
+
return if success then Some node else None
130
+
}
131
+
132
+
let persister (n : MstNode) = async {
133
+
let bytes = Mst.serialize n
134
+
let cid = Cid(Crypto.sha256 bytes)
135
+
let key = System.Convert.ToBase64String(cid.Bytes)
136
+
store.[key] <- n
137
+
return cid
138
+
}
139
+
140
+
let mutable node = { Left = None; Entries = [] }
141
+
142
+
let k1, v1 = "apple", Cid(Crypto.sha256Str "1")
143
+
let k2, v2 = "banana", Cid(Crypto.sha256Str "2")
144
+
let k3, v3 = "cherry", Cid(Crypto.sha256Str "3")
145
+
146
+
node <- Mst.put loader persister node k1 v1 "" |> Async.RunSynchronously
147
+
node <- Mst.put loader persister node k2 v2 "" |> Async.RunSynchronously
148
+
node <- Mst.put loader persister node k3 v3 "" |> Async.RunSynchronously
149
+
150
+
let g1 = Mst.get loader node "apple" "" |> Async.RunSynchronously
151
+
let g2 = Mst.get loader node "banana" "" |> Async.RunSynchronously
152
+
let g3 = Mst.get loader node "cherry" "" |> Async.RunSynchronously
153
+
154
+
Assert.Equal(Some v1, g1)
155
+
Assert.Equal(Some v2, g2)
156
+
Assert.Equal(Some v3, g3)
157
+
158
+
[<Fact>]
159
+
let ``Put Operation Multiple Reverse`` () =
160
+
let store = System.Collections.Concurrent.ConcurrentDictionary<string, MstNode>()
161
+
162
+
let loader (c : Cid) = async {
163
+
let key = System.Convert.ToBase64String(c.Bytes)
164
+
let success, node = store.TryGetValue(key)
165
+
return if success then Some node else None
166
+
}
167
+
168
+
let persister (n : MstNode) = async {
169
+
let bytes = Mst.serialize n
170
+
let cid = Cid(Crypto.sha256 bytes)
171
+
let key = System.Convert.ToBase64String(cid.Bytes)
172
+
store.[key] <- n
173
+
return cid
174
+
}
175
+
176
+
let mutable node = { Left = None; Entries = [] }
177
+
178
+
let data = [ "zebra"; "yak"; "xylophone" ]
179
+
180
+
for k in data do
181
+
let v = Cid(Crypto.sha256Str k)
182
+
node <- Mst.put loader persister node k v "" |> Async.RunSynchronously
183
+
184
+
for k in data do
185
+
let expected = Cid(Crypto.sha256Str k)
186
+
let actual = Mst.get loader node k "" |> Async.RunSynchronously
187
+
Assert.Equal(Some expected, actual)
188
+
189
+
[<Fact>]
190
+
let ``Delete Operation Simple`` () =
191
+
let store = System.Collections.Concurrent.ConcurrentDictionary<string, MstNode>()
192
+
193
+
let loader (c : Cid) = async {
194
+
let key = System.Convert.ToBase64String(c.Bytes)
195
+
let success, node = store.TryGetValue(key)
196
+
return if success then Some node else None
197
+
}
198
+
199
+
let persister (n : MstNode) = async {
200
+
let bytes = Mst.serialize n
201
+
let cid = Cid(Crypto.sha256 bytes)
202
+
let key = System.Convert.ToBase64String(cid.Bytes)
203
+
store.[key] <- n
204
+
return cid
205
+
}
206
+
207
+
let mutable node = { Left = None; Entries = [] }
208
+
let cid1 = Cid(Crypto.sha256Str "val1")
209
+
210
+
node <- Mst.put loader persister node "apple" cid1 "" |> Async.RunSynchronously
211
+
212
+
let res1 = Mst.get loader node "apple" "" |> Async.RunSynchronously
213
+
Assert.Equal(Some cid1, res1)
214
+
215
+
// Delete
216
+
let nodeOpt = Mst.delete loader persister node "apple" "" |> Async.RunSynchronously
217
+
218
+
match nodeOpt with
219
+
| None -> ()
220
+
| Some n ->
221
+
let res2 = Mst.get loader n "apple" "" |> Async.RunSynchronously
222
+
Assert.True(Option.isNone res2)
223
+
224
+
[<Fact>]
225
+
let ``Determinism From Entries`` () =
226
+
let store = System.Collections.Concurrent.ConcurrentDictionary<string, MstNode>()
227
+
228
+
let loader (c : Cid) = async {
229
+
let key = System.Convert.ToBase64String(c.Bytes)
230
+
let success, node = store.TryGetValue(key)
231
+
return if success then Some node else None
232
+
}
233
+
234
+
let persister (n : MstNode) = async {
235
+
let bytes = Mst.serialize n
236
+
let cid = Cid(Crypto.sha256 bytes)
237
+
let key = System.Convert.ToBase64String(cid.Bytes)
238
+
store.[key] <- n
239
+
return cid
240
+
}
241
+
242
+
let data = [
243
+
"apple", Cid(Crypto.sha256Str "1")
244
+
"banana", Cid(Crypto.sha256Str "2")
245
+
"cherry", Cid(Crypto.sha256Str "3")
246
+
"date", Cid(Crypto.sha256Str "4")
247
+
"elderberry", Cid(Crypto.sha256Str "5")
248
+
]
249
+
250
+
251
+
let node1 = Mst.fromEntries loader persister data |> Async.RunSynchronously
252
+
let cid1 = persister node1 |> Async.RunSynchronously
253
+
254
+
let node2 =
255
+
Mst.fromEntries loader persister (List.rev data) |> Async.RunSynchronously
256
+
257
+
let cid2 = persister node2 |> Async.RunSynchronously
258
+
let data3 = [ data.[2]; data.[0]; data.[4]; data.[1]; data.[3] ]
259
+
let node3 = Mst.fromEntries loader persister data3 |> Async.RunSynchronously
260
+
let cid3 = persister node3 |> Async.RunSynchronously
261
+
262
+
Assert.Equal(cid1, cid2)
263
+
Assert.Equal(cid1, cid3)
264
+
()
+4
-3
PDSharp.Tests/PDSharp.Tests.fsproj
+4
-3
PDSharp.Tests/PDSharp.Tests.fsproj
···
8
8
9
9
<ItemGroup>
10
10
<Compile Include="Tests.fs" />
11
+
<Compile Include="Mst.Tests.fs" />
11
12
<Compile Include="Program.fs" />
12
13
</ItemGroup>
13
14
···
18
19
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
19
20
</ItemGroup>
20
21
21
-
<ItemGroup>
22
-
<ProjectReference Include="..\PDSharp\PDSharp.fsproj" />
23
-
<ProjectReference Include="..\PDSharp.Core\PDSharp.Core.fsproj" />
22
+
<ItemGroup>
23
+
<ProjectReference Include="..\PDSharp\PDSharp.fsproj" />
24
+
<ProjectReference Include="..\PDSharp.Core\PDSharp.Core.fsproj" />
24
25
</ItemGroup>
25
26
26
27
</Project>
+4
-4
roadmap.txt
+4
-4
roadmap.txt
···
23
23
--------------------------------------------------------------------------------
24
24
Milestone D: MST Implementation
25
25
--------------------------------------------------------------------------------
26
-
- Merkle Search Tree per repository spec
27
-
- Key depth = leading zero bits in SHA-256(key) counted in 2-bit chunks
28
-
- Node encoding: (l, e[p,k,v,t]) with key prefix compression
26
+
- [x] Merkle Search Tree per repository spec
27
+
- [x] Key depth = leading zero bits in SHA-256(key) counted in 2-bit chunks
28
+
- [x] Node encoding: (l, e[p,k,v,t]) with key prefix compression
29
29
DoD: Insert/update/delete yields reproducible root CID
30
30
--------------------------------------------------------------------------------
31
31
Milestone E: Commit + BlockStore + putRecord
···
117
117
[ ] describeServer endpoint working
118
118
[ ] Crypto primitives (sha256, ECDSA p256/k256, low-S)
119
119
[ ] DAG-CBOR + CID generation correct
120
-
[ ] MST producing deterministic root CIDs
120
+
[x] MST producing deterministic root CIDs
121
121
[ ] putRecord + blockstore operational
122
122
[ ] CAR export + sync endpoints
123
123
[ ] subscribeRepos firehose