Skip to content

Commit 8138341

Browse files
inclusion/exclusion proofs - very untested
1 parent 3f2ecfb commit 8138341

File tree

7 files changed

+81
-24
lines changed

7 files changed

+81
-24
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,5 @@ publishing to pypi: (this one is mainly for my benefit!)
3939

4040
```
4141
python3 -m build
42-
python3 -m twine upload --repository pypi dist/*
42+
python3 -m twine upload --repository pypi dist/atmst-*.tar.gz
4343
```

src/atmst/blockstore/car_file.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class ReadOnlyCARBlockStore(BlockStore):
3838
proofs provided in CAR format, and for testing.
3939
"""
4040

41-
car_roots: List[CID]
41+
car_root: CID
4242
block_offsets: Dict[bytes, Tuple[int, int]] # CID -> (offset, length)
4343

4444
def __init__(self, file: BinaryIO, validate_hashes: bool=True) -> None:

src/atmst/cartool.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def dump_all(car_path: str):
5757

5858
def dump_record(car_path: str, key: str):
5959
bs, commit = open_car(car_path)
60-
val = NodeWalker(NodeStore(bs), commit["data"]).find_value(key)
60+
val = NodeWalker(NodeStore(bs), commit["data"]).find_rpath(key)
6161
if val is None:
6262
print("Record not found!", file=sys.stderr)
6363
sys.exit(-1)

src/atmst/mst/diff.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,15 +134,15 @@ def _mst_diff_recursive(created: Set[CID], deleted: Set[CID], a: NodeWalker, b:
134134
a.down()
135135
deleted.add(a.frame.node.cid)
136136
else:
137-
a.right()
137+
a.right_or_up()
138138

139139
# catch up cursor b, likewise
140140
while b.rpath < a.rpath and not b.is_final:
141141
if b.subtree: # recurse down every subtree
142142
b.down()
143143
created.add(b.frame.node.cid)
144144
else:
145-
b.right()
145+
b.right_or_up()
146146

147147
# the rpaths now match, but the subrees below us might not
148148

@@ -153,5 +153,5 @@ def _mst_diff_recursive(created: Set[CID], deleted: Set[CID], a: NodeWalker, b:
153153
if a.rpath == a.stack[0].rpath and b.rpath == b.stack[0].rpath:
154154
break
155155

156-
a.right()
157-
b.right()
156+
a.right_or_up()
157+
b.right_or_up()

src/atmst/mst/node_walker.py

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ def subtree_walker(self) -> "Self":
5454
def frame(self) -> StackFrame:
5555
return self.stack[-1]
5656

57+
@property
58+
def height(self) -> int:
59+
return self.frame.node.height
60+
5761
@property
5862
def lpath(self) -> str:
5963
return self.frame.lpath if self.frame.idx == 0 else self.frame.node.keys[self.frame.idx - 1]
@@ -76,21 +80,31 @@ def rval(self) -> Optional[CID]:
7680

7781
@property
7882
def is_final(self) -> bool:
83+
# is (not self.stack) really necesasry here? is that a reachable state?
7984
return (not self.stack) or (self.subtree is None and self.rpath == self.stack[0].rpath)
8085

81-
def right(self) -> None:
82-
if (self.frame.idx + 1) >= len(self.frame.node.subtrees):
86+
@property
87+
def can_go_right(self) -> bool:
88+
return (self.frame.idx + 1) < len(self.frame.node.subtrees)
89+
90+
def right_or_up(self) -> None:
91+
if not self.can_go_right:
8392
# we reached the end of this node, go up a level
8493
self.stack.pop()
8594
if not self.stack:
8695
raise StopIteration # you probably want to check .final instead of hitting this
87-
return self.right() # we need to recurse, to skip over empty intermediates on the way back up
96+
return self.right_or_up() # we need to recurse, to skip over empty intermediates on the way back up
97+
self.frame.idx += 1
98+
99+
def right(self) -> None:
100+
if not self.can_go_right:
101+
raise Exception("cursor is already at rightmost position in node")
88102
self.frame.idx += 1
89103

90104
def down(self) -> None:
91105
subtree = self.frame.node.subtrees[self.frame.idx]
92106
if subtree is None:
93-
raise Exception("oi, you can't recurse here mate")
107+
raise Exception("oi, you can't recurse here mate (subtree is None)")
94108

95109
self.stack.append(self.StackFrame(
96110
node=self.ns.get_node(subtree),
@@ -105,7 +119,7 @@ def down(self) -> None:
105119
def next_kv(self) -> Tuple[str, CID]:
106120
while self.subtree: # recurse down every subtree
107121
self.down()
108-
self.right()
122+
self.right_or_up()
109123
return self.lpath, self.lval # the kv pair we just jumped over
110124

111125
# iterate over every k/v pair in key-sorted order
@@ -121,7 +135,7 @@ def iter_nodes(self) -> Iterable[MSTNode]:
121135
while self.subtree: # recurse down every subtree
122136
self.down()
123137
yield self.frame.node
124-
self.right()
138+
self.right_or_up()
125139

126140
def iter_node_cids(self) -> Iterable[CID]:
127141
for node in self.iter_nodes():
@@ -131,7 +145,7 @@ def iter_node_cids(self) -> Iterable[CID]:
131145
def iter_kv_range(self, start: str, end: str, end_inclusive: bool=False) -> Iterable[Tuple[str, CID]]:
132146
while True:
133147
while self.rpath < start:
134-
self.right()
148+
self.right_or_up()
135149
if not self.subtree:
136150
break
137151
self.down()
@@ -140,14 +154,22 @@ def iter_kv_range(self, start: str, end: str, end_inclusive: bool=False) -> Iter
140154
if k > end or (not end_inclusive and k == end):
141155
break
142156
yield k, v
143-
144-
def find_value(self, key: str) -> Optional[CID]:
157+
158+
# TODO: we need to make this early-exit so that it can work with concise deletion proofs, maybe
159+
# (early exit based on key height - might need significant rewrite)
160+
def find_rpath(self, rpath: str) -> Optional[CID]:
161+
rpath_height = MSTNode.key_height(rpath)
145162
while True:
146-
while self.rpath < key:
163+
# if the rpath we're looking for is higher than the current cursor,
164+
# we're never going to find it (i.e. we early-exit)
165+
if rpath_height > self.height:
166+
return None
167+
while self.rpath < rpath: # either look for the rpath, or the right point to go down
168+
if not self.can_go_right:
169+
return None
147170
self.right()
148-
if self.rpath == key or not self.subtree:
149-
break
171+
if self.rpath == rpath:
172+
return self.rval # found it!
173+
if not self.subtree:
174+
return None # need to go down, but we can't
150175
self.down()
151-
if self.rpath != key:
152-
return None
153-
return self.rval

src/atmst/mst/proof.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from typing import Set, Tuple, Optional
2+
3+
from cbrrr import CID
4+
5+
from .node import MSTNode
6+
from .node_store import NodeStore
7+
from .node_walker import NodeWalker
8+
9+
class InvalidProof(Exception):
10+
pass
11+
12+
# works for both inclusion and exclusion proofs
13+
def find_rpath_and_build_proof(ns: NodeStore, root_cid: CID, rpath: str) -> Tuple[Optional[CID], Set[CID]]:
14+
walker = NodeWalker(ns, root_cid)
15+
value = walker.find_rpath(rpath) # returns None if not found
16+
proof = {frame.node.cid for frame in walker.stack}
17+
return value, proof
18+
19+
def verify_inclusion(ns: NodeStore, root_cid: CID, rpath: str) -> None:
20+
walker = NodeWalker(ns, root_cid)
21+
try:
22+
if walker.find_rpath(rpath) is None:
23+
raise InvalidProof("rpath not present in MST")
24+
except KeyError:
25+
raise InvalidProof("missing MST blocks")
26+
27+
def verify_exclusion(ns: NodeStore, root_cid: CID, rpath: str) -> None:
28+
walker = NodeWalker(ns, root_cid)
29+
try:
30+
if walker.find_rpath(rpath) is not None:
31+
raise InvalidProof("rpath *is* present in MST")
32+
except KeyError:
33+
raise InvalidProof("missing MST blocks")

tests/test_mst_diff.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@ def setUp(self):
1919
i = 0
2020
for height in [0, 1, 0, 2, 0, 1, 0]: # if all these keys are added to a MST, it'll form a perfect binary tree.
2121
while True:
22-
key = f"{i:04d}"
22+
key = f"k/{i:02d}"
2323
i += 1
2424
if MSTNode.key_height(key) == height:
2525
keys.append(key)
2626
break
27-
27+
28+
#print(keys)
29+
2830
bs = MemoryBlockStore()
2931
self.ns = NodeStore(bs)
3032
wrangler = NodeWrangler(self.ns)

0 commit comments

Comments
 (0)