-
Notifications
You must be signed in to change notification settings - Fork 67
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proof of absence and presence for the same path with nil proof of presence value #414
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -398,8 +398,9 @@ func PreStateTreeFromProof(proof *Proof, rootC *Point) (VerkleNode, error) { // | |
stems = append(stems, k[:31]) | ||
} | ||
} | ||
stemIndex := 0 | ||
|
||
if len(stems) != len(proof.ExtStatus) { | ||
return nil, fmt.Errorf("invalid number of stems and extension statuses: %d != %d", len(stems), len(proof.ExtStatus)) | ||
} | ||
Comment on lines
+401
to
+403
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As a preliminary check, now check if there's a 1:1 match between stems and extension statuses. |
||
var ( | ||
info = map[string]stemInfo{} | ||
paths [][]byte | ||
|
@@ -412,81 +413,73 @@ func PreStateTreeFromProof(proof *Proof, rootC *Point) (VerkleNode, error) { // | |
return nil, fmt.Errorf("proof of absence stems are not sorted") | ||
} | ||
|
||
// assign one or more stem to each stem info | ||
// We build a cache of paths that have a presence extension status. | ||
pathsWithExtPresent := map[string]struct{}{} | ||
i := 0 | ||
for _, es := range proof.ExtStatus { | ||
depth := es >> 3 | ||
path := stems[stemIndex][:depth] | ||
if es&3 == extStatusPresent { | ||
pathsWithExtPresent[string(stems[i][:es>>3])] = struct{}{} | ||
jsign marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
i++ | ||
} | ||
Comment on lines
+416
to
+424
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nothing new here compared to last review. |
||
|
||
// assign one or more stem to each stem info | ||
for i, es := range proof.ExtStatus { | ||
si := stemInfo{ | ||
depth: depth, | ||
depth: es >> 3, | ||
stemType: es & 3, | ||
} | ||
path := stems[i][:si.depth] | ||
switch si.stemType { | ||
case extStatusAbsentEmpty: | ||
// All keys that are part of a proof of absence, must contain empty | ||
// prestate values. If that isn't the case, the proof is invalid. | ||
for i, k := range proof.Keys { // TODO: DoS risk, use map or binary search. | ||
if bytes.HasPrefix(k, path) { | ||
if proof.PreValues[i] != nil { | ||
return nil, fmt.Errorf("proof of absence (empty) stem %x has a value", si.stem) | ||
} | ||
for j := range proof.Keys { // TODO: DoS risk, use map or binary search. | ||
if bytes.HasPrefix(proof.Keys[j], stems[i]) && proof.PreValues[j] != nil { | ||
return nil, fmt.Errorf("proof of absence (empty) stem %x has a value", si.stem) | ||
} | ||
} | ||
case extStatusAbsentOther: | ||
si.stem = poas[0] | ||
poas = poas[1:] | ||
// All keys that are part of a proof of absence, must contain empty | ||
// prestate values. If that isn't the case, the proof is invalid. | ||
for i, k := range proof.Keys { // TODO: DoS risk, use map or binary search. | ||
if bytes.HasPrefix(k, si.stem) { | ||
if proof.PreValues[i] != nil { | ||
return nil, fmt.Errorf("proof of absence (other) stem %x has a value", si.stem) | ||
} | ||
for j := range proof.Keys { // TODO: DoS risk, use map or binary search. | ||
if bytes.HasPrefix(proof.Keys[j], stems[i]) && proof.PreValues[j] != nil { | ||
return nil, fmt.Errorf("proof of absence (other) stem %x has a value", si.stem) | ||
} | ||
} | ||
Comment on lines
+445
to
449
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. First, we check that all keys for this stem have |
||
default: | ||
// the first stem could be missing (e.g. the second stem in the | ||
// group is the one that is present. Compare each key to the first | ||
// stem, along the length of the path only. | ||
stemPath := stems[stemIndex][:len(path)] | ||
|
||
// For this absent path, we must first check if this path contains a proof of presence. | ||
// If that is the case, we don't have to do anything since the corresponding leaf will be | ||
// constructed by that extension status (already processed or to be processed). | ||
// In other case, we should get the stem from the list of proof of absence stems. | ||
if _, ok := pathsWithExtPresent[string(path)]; ok { | ||
continue | ||
} | ||
Comment on lines
+451
to
+457
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Second, if we know there is a proof of presence for this path; we don't do anything since that proof of presence will create this path. |
||
|
||
// Note that this path doesn't have proof of presence (previous if check above), but | ||
// it can have multiple proof of absence. If a previous proof of absence had already | ||
// created the stemInfo for this path, we don't have to do anything. | ||
if _, ok := info[string(path)]; ok { | ||
continue | ||
} | ||
Comment on lines
+459
to
+464
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Third, a small border case -- if a previous proof of absence already created this path, we don't have to do anything. |
||
|
||
si.stem = poas[0] | ||
poas = poas[1:] | ||
Comment on lines
+466
to
+467
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Forth, we're the first (or only) proof of absence (without proof of presences) for this path. Create it. |
||
case extStatusPresent: | ||
jsign marked this conversation as resolved.
Show resolved
Hide resolved
|
||
si.values = map[byte][]byte{} | ||
for i, k := range proof.Keys { // TODO: DoS risk, use map or binary search. | ||
if bytes.Equal(k[:len(path)], stemPath) && proof.PreValues[i] != nil { | ||
si.values[k[31]] = proof.PreValues[i] | ||
si.stem = stems[i] | ||
for j, k := range proof.Keys { // TODO: DoS risk, use map or binary search. | ||
if bytes.Equal(k[:31], si.stem) { | ||
si.values[k[31]] = proof.PreValues[j] | ||
si.has_c1 = si.has_c1 || (k[31] < 128) | ||
si.has_c2 = si.has_c2 || (k[31] >= 128) | ||
// This key has values, its stem is the one that | ||
// is present. | ||
if si.stem == nil { | ||
si.stem = k[:31] | ||
continue | ||
} | ||
// Any other key with values must have the same | ||
// same previously detected stem. If that isn't the case, | ||
// the proof is invalid. | ||
if !bytes.Equal(si.stem, k[:31]) { | ||
return nil, fmt.Errorf("multiple keys with values found for stem %x", k[:31]) | ||
} | ||
jsign marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
// For a proof of presence, we must always have detected a stem. | ||
// If that isn't the case, the proof is invalid. | ||
if si.stem == nil { | ||
return nil, fmt.Errorf("no stem found for path %x", path) | ||
} | ||
Comment on lines
469
to
-475
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All this got quite simplified since:
|
||
default: | ||
return nil, fmt.Errorf("invalid extension status: %d", si.stemType) | ||
} | ||
info[string(path)] = si | ||
paths = append(paths, path) | ||
|
||
// Skip over all the stems that share the same path | ||
// to the extension tree. This happens e.g. if two | ||
// stems have the same path, but one is a proof of | ||
// absence and the other one is present. | ||
stemIndex++ | ||
for ; stemIndex < len(stems); stemIndex++ { | ||
if !bytes.Equal(stems[stemIndex][:depth], path) { | ||
break | ||
} | ||
} | ||
Comment on lines
-479
to
-489
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks to the 1:1 correspondence, all this is unnecessary since we don't have to "infer" the current stem by "tracking" some separate index from keys. |
||
} | ||
|
||
if len(poas) != 0 { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1464,34 +1464,39 @@ func (n *LeafNode) GetProofItems(keys keylist, _ NodeResolverFn) (*ProofElements | |
pe.Fis = append(pe.Fis, poly[:]) | ||
} | ||
|
||
addedAbsentStems := map[string]struct{}{} | ||
|
||
// Second pass: add the cn-level elements | ||
for _, key := range keys { | ||
pe.ByPath[string(key[:n.depth])] = n.commitment | ||
|
||
// Proof of absence: case of a differing stem. | ||
// Add an unopened stem-level node. | ||
if !equalPaths(n.stem, key) { | ||
// Corner case: don't add the poa stem if it's | ||
// already present as a proof-of-absence for a | ||
// different key, or for the same key (case of | ||
// multiple missing keys being absent). | ||
// The list of extension statuses has to be of | ||
// length 1 at this level, so skip otherwise. | ||
// If this is the first extension status added for this path, | ||
// add the proof of absence stem (only once). If later we detect a proof of | ||
// presence, we'll clear the list since that proof of presence | ||
// will be enough to provide the stem. | ||
if len(esses) == 0 { | ||
esses = append(esses, extStatusAbsentOther|(n.depth<<3)) | ||
poass = append(poass, n.stem) | ||
} | ||
Comment on lines
+1475
to
1481
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What comment says. |
||
// Add an extension status absent other for this stem. | ||
// Note we keep a cache to avoid adding the same stem twice (or more) if | ||
// there're multiple keys with the same stem. | ||
if _, ok := addedAbsentStems[string(key[:StemSize])]; !ok { | ||
esses = append(esses, extStatusAbsentOther|(n.depth<<3)) | ||
addedAbsentStems[string(key[:StemSize])] = struct{}{} | ||
} | ||
Comment on lines
+1482
to
+1488
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We use the |
||
pe.Vals = append(pe.Vals, nil) | ||
continue | ||
} | ||
|
||
// corner case (see previous corner case): if a proof-of-absence | ||
// stem was found, and it now turns out the same stem is used as | ||
// a proof of presence, clear the proof-of-absence list to avoid | ||
// redundancy. | ||
// As mentioned above, if a proof-of-absence stem was found, and | ||
// it now turns out the same stem is used as a proof of presence, | ||
// clear the proof-of-absence list to avoid redundancy. Note that | ||
// we don't delete the extension statuse since that is needed to | ||
// figure out which is the correct stem for this path. | ||
if len(poass) > 0 { | ||
poass = nil | ||
esses = nil | ||
jsign marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
var ( | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need this anymore, things got simplified.