Add portal proof functionality for non-existing keys/paths (#2610)

This commit is contained in:
Jordan Hrycaj 2024-09-11 09:39:45 +00:00 committed by GitHub
parent 1ced684d8f
commit 75808bc03b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 140 additions and 57 deletions

View File

@ -330,16 +330,20 @@ type
AristoApiPartAccountTwig* =
proc(db: AristoDbRef;
accPath: Hash256;
): Result[seq[Blob], AristoError]
): Result[(seq[Blob],bool), AristoError]
{.noRaise.}
## This function returns a chain of rlp-encoded nodes along the argument
## path `(root,path)`.
## path `(root,path)` followed by a `true` value if the `path` argument
## exists in the database. If the argument `path` is not on the database,
## a partial path will be returned follwed by a `false` value.
##
## Errors will only be returned for invalid paths.
AristoApiPartGenericTwig* =
proc(db: AristoDbRef;
root: VertexID;
path: openArray[byte];
): Result[seq[Blob], AristoError]
): Result[(seq[Blob],bool), AristoError]
{.noRaise.}
## Variant of `partAccountTwig()`.
##
@ -350,30 +354,33 @@ type
proc(db: AristoDbRef;
accPath: Hash256;
stoPath: Hash256;
): Result[seq[Blob], AristoError]
): Result[(seq[Blob],bool), AristoError]
{.noRaise.}
## Variant of `partAccountTwig()`.
## Variant of `partAccountTwig()`. Note that the function always returns
## an error unless the `accPath` is valid.
AristoApiPartUntwigGeneric* =
proc(chain: openArray[Blob];
root: Hash256;
path: openArray[byte];
): Result[Blob,AristoError]
): Result[Opt[Blob],AristoError]
{.noRaise.}
## Follow and verify the argument `chain` up unlil the last entry
## which must be a leaf node. Extract the payload and pass it on
## as return code.
## Follow and verify the argument `chain` up unlil the last entry which
## must be a leaf node. Extract the payload and pass it on as return
## code. If a `Opt.none()` result is returned then the `path` argument
## does provably not exist relative to `chain`.
AristoApiPartUntwigGenericOk* =
proc(chain: openArray[Blob];
root: Hash256;
path: openArray[byte];
payload: openArray[byte];
payload: Opt[Blob];
): Result[void,AristoError]
{.noRaise.}
## Variant of `partUntwigGeneric()`. The function verifis the argument
## Variant of `partUntwigGeneric()`. The function verifies the argument
## `chain` of rlp-encoded nodes against the `path` and `payload`
## arguments.
## arguments. If `payload` is passed `Opt.none()`, then the function is
## subject to proving that the `path` does not exist relaive to `chain`.
##
## Note: This function provides a functionality comparable to the
## `isValidBranch()` function from `hexary.nim`.
@ -382,7 +389,7 @@ type
proc(chain: openArray[Blob];
root: Hash256;
path: Hash256;
): Result[Blob,AristoError]
): Result[Opt[Blob],AristoError]
{.noRaise.}
## Variant of `partUntwigGeneric()`.
@ -390,7 +397,7 @@ type
proc(chain: openArray[Blob];
root: Hash256;
path: Hash256;
payload: openArray[byte];
payload: Opt[Blob];
): Result[void,AristoError]
{.noRaise.}
## Variant of `partUntwigGenericOk()`.
@ -974,7 +981,7 @@ func init*(
result = api.partUntwigGeneric(a, b, c)
profApi.partUntwigGenericOk =
proc(a: openArray[Blob]; b: Hash256; c, d: openArray[byte]): auto =
proc(a: openArray[Blob]; b:Hash256; c:openArray[byte]; d:Opt[Blob]): auto =
AristoApiProfPartUntwigGenericOkFn.profileRunner:
result = api.partUntwigGenericOk(a, b, c, d)
@ -984,7 +991,7 @@ func init*(
result = api.partUntwigPath(a, b, c)
profApi.partUntwigPathOk =
proc(a: openArray[Blob]; b, c: Hash256; d: openArray[byte]): auto =
proc(a: openArray[Blob]; b, c: Hash256; d: Opt[Blob]): auto =
AristoApiProfPartUntwigPathOkFn.profileRunner:
result = api.partUntwigPathOk(a, b, c, d)

View File

@ -27,7 +27,7 @@ proc checkTwig*(
let
proof = ? db.partGenericTwig(root, path)
key = ? db.computeKey (root,root)
pyl = ? proof.partUntwigGeneric(key.to(Hash256), path)
pyl = ? proof[0].partUntwigGeneric(key.to(Hash256), path)
ok()
@ -40,7 +40,7 @@ proc checkTwig*(
proof = ? db.partStorageTwig(accPath, stoPath)
vid = ? db.fetchStorageID accPath
key = ? db.computeKey (VertexID(1),vid)
pyl = ? proof.partUntwigPath(key.to(Hash256), stoPath)
pyl = ? proof[0].partUntwigPath(key.to(Hash256), stoPath)
ok()

View File

@ -170,6 +170,7 @@ type
PartChkVidTabCoreRootMissing
PartChkVidTabVidMissing
PartChnBranchPathExhausted
PartChnBranchVoidEdge
PartChnExtPfxMismatch
PartChnLeafPathMismatch
PartChnNodeConvError

View File

@ -70,19 +70,28 @@ proc partGenericTwig*(
db: AristoDbRef;
root: VertexID;
path: NibblesBuf;
): Result[seq[Blob], AristoError] =
): Result[(seq[Blob],bool), AristoError] =
## This function returns a chain of rlp-encoded nodes along the argument
## path `(root,path)`.
## path `(root,path)` followed by a `true` value if the `path` argument
## exists in the database. If the argument `path` is not on the database,
## a partial path will be returned follwed by a `false` value.
##
## Errors will only be returned for invalid paths.
##
var chain: seq[Blob]
? db.chainRlpNodes((root,root), path, chain)
ok chain
let rc = db.chainRlpNodes((root,root), path, chain)
if rc.isOk:
ok((chain, true))
elif rc.error in ChainRlpNodesNoEntry:
ok((chain, false))
else:
err(rc.error)
proc partGenericTwig*(
db: AristoDbRef;
root: VertexID;
path: openArray[byte];
): Result[seq[Blob], AristoError] =
): Result[(seq[Blob],bool), AristoError] =
## Variant of `partGenericTwig()`.
##
## Note: This function provides a functionality comparable to the
@ -93,7 +102,7 @@ proc partGenericTwig*(
proc partAccountTwig*(
db: AristoDbRef;
accPath: Hash256;
): Result[seq[Blob], AristoError] =
): Result[(seq[Blob],bool), AristoError] =
## Variant of `partGenericTwig()`.
db.partGenericTwig(VertexID(1), NibblesBuf.fromBytes accPath.data)
@ -101,8 +110,9 @@ proc partStorageTwig*(
db: AristoDbRef;
accPath: Hash256;
stoPath: Hash256;
): Result[seq[Blob], AristoError] =
## Variant of `partGenericTwig()`.
): Result[(seq[Blob],bool), AristoError] =
## Variant of `partGenericTwig()`. Note that the function always returns an
## error unless the `accPath` is valid.
let vid = ? db.fetchStorageID accPath
db.partGenericTwig(vid, NibblesBuf.fromBytes stoPath.data)
@ -112,11 +122,19 @@ proc partUntwigGeneric*(
chain: openArray[Blob];
root: Hash256;
path: openArray[byte];
): Result[Blob,AristoError] =
## Verify the chain of rlp-encoded nodes and return the payload.
): Result[Opt[Blob],AristoError] =
## Verify the chain of rlp-encoded nodes and return the payload. If a
## `Opt.none()` result is returned then the `path` argument does provably
## not exist relative to `chain`.
try:
let nibbles = NibblesBuf.fromBytes path
return chain.trackRlpNodes(root.to(HashKey), nibbles, start=true)
let
nibbles = NibblesBuf.fromBytes path
rc = chain.trackRlpNodes(root.to(HashKey), nibbles, start=true)
if rc.isOk:
return ok(Opt.some rc.value)
if rc.error in TrackRlpNodesNoEntry:
return ok(Opt.none Blob)
return err(rc.error)
except RlpError:
return err(PartTrkRlpError)
@ -124,7 +142,7 @@ proc partUntwigPath*(
chain: openArray[Blob];
root: Hash256;
path: Hash256;
): Result[Blob,AristoError] =
): Result[Opt[Blob],AristoError] =
## Variant of `partUntwigGeneric()`.
chain.partUntwigGeneric(root, path.data)
@ -133,7 +151,7 @@ proc partUntwigGenericOk*(
chain: openArray[Blob];
root: Hash256;
path: openArray[byte];
payload: openArray[byte];
payload: Opt[Blob];
): Result[void,AristoError] =
## Verify the argument `chain` of rlp-encoded nodes against the `path`
## and `payload` arguments.
@ -150,7 +168,7 @@ proc partUntwigPathOk*(
chain: openArray[Blob];
root: Hash256;
path: Hash256;
payload: openArray[byte];
payload: Opt[Blob];
): Result[void,AristoError] =
## Variant of `partUntwigGenericOk()`.
chain.partUntwigGenericOk(root, path.data, payload)

View File

@ -15,6 +15,16 @@ import
results,
".."/[aristo_desc, aristo_get, aristo_utils, aristo_compute, aristo_serialise]
const
ChainRlpNodesNoEntry* = {
PartChnLeafPathMismatch, PartChnExtPfxMismatch, PartChnBranchVoidEdge}
## Partial path errors that can be used to proof that a path does
## not exists.
TrackRlpNodesNoEntry* = {PartTrkLinkExpected, PartTrkLeafPfxMismatch}
## This is the opposite of `ChainRlpNodesNoEntry` when verifying that a
## node does not exist.
# ------------------------------------------------------------------------------
# Public functions
# ------------------------------------------------------------------------------
@ -53,6 +63,8 @@ proc chainRlpNodes*(
let
nibble = path[nChewOff]
rest = path.slice(nChewOff+1)
if not vtx.bVid[nibble].isValid:
return err(PartChnBranchVoidEdge)
# Recursion!
db.chainRlpNodes((rvid.root,vtx.bVid[nibble]), rest, chain)

View File

@ -212,7 +212,7 @@ proc verify*(
proof: openArray[Blob];
root: Hash256;
path: openArray[byte];
): CoreDbRc[Blob] =
): CoreDbRc[Opt[Blob]] =
## This function os the counterpart of any of the `proof()` functions. Given
## the argument chain of rlp-encoded nodes `proof`, this function verifies
## that the chain represents a partial MPT starting with a root node state
@ -243,7 +243,7 @@ proc verifyOk*(
proof: openArray[Blob];
root: Hash256;
path: openArray[byte];
payload: openArray[byte];
payload: Opt[Blob];
): CoreDbRc[void] =
## Variant of `verify()` which directly checks the argument `payload`
## against what would be the return code in `verify()`.
@ -267,7 +267,7 @@ proc verify*(
proof: openArray[Blob];
root: Hash256;
path: Hash256;
): CoreDbRc[Blob] =
): CoreDbRc[Opt[Blob]] =
## Variant of `verify()`.
template mpt: untyped =
when db is CoreDbRef:
@ -288,7 +288,7 @@ proc verifyOk*(
proof: openArray[Blob];
root: Hash256;
path: Hash256;
payload: openArray[byte];
payload: Opt[Blob];
): CoreDbRc[void] =
## Variant of `verifyOk()`.
template mpt: untyped =
@ -432,9 +432,12 @@ proc getGeneric*(
proc proof*(
mpt: CoreDbMptRef;
key: openArray[byte];
): CoreDbRc[seq[Blob]] =
): CoreDbRc[(seq[Blob],bool)] =
## On the generic MPT, collect the nodes along the `key` interpreted as
## path. Return these path nodes as a chain of rlp-encoded blobs.
## path. Return these path nodes as a chain of rlp-encoded blobs followed
## by a bool value which is `true` if the `key` path exists in the database,
## and `false` otherwise. In the latter case, the chain of rlp-encoded blobs
## are the nodes proving that the `key` path does not exist.
##
mpt.setTrackNewApi MptProofFn
result = block:
@ -547,9 +550,12 @@ proc getAccounts*(ctx: CoreDbCtxRef): CoreDbAccRef =
proc proof*(
acc: CoreDbAccRef;
accPath: Hash256;
): CoreDbRc[seq[Blob]] =
): CoreDbRc[(seq[Blob],bool)] =
## On the accounts MPT, collect the nodes along the `accPath` interpreted as
## path. Return these path nodes as a chain of rlp-encoded blobs.
## path. Return these path nodes as a chain of rlp-encoded blobs followed
## by a bool value which is `true` if the `key` path exists in the database,
## and `false` otherwise. In the latter case, the chain of rlp-encoded blobs
## are the nodes proving that the `key` path does not exist.
##
acc.setTrackNewApi AccProofFn
result = block:
@ -671,10 +677,16 @@ proc slotProof*(
acc: CoreDbAccRef;
accPath: Hash256;
stoPath: Hash256;
): CoreDbRc[seq[Blob]] =
): CoreDbRc[(seq[Blob],bool)] =
## On the storage MPT related to the argument account `acPath`, collect the
## nodes along the `stoPath` interpreted as path. Return these path nodes as
## a chain of rlp-encoded blobs.
## a chain of rlp-encoded blobs followed by a bool value which is `true` if
## the `key` path exists in the database, and `false` otherwise. In the
## latter case, the chain of rlp-encoded blobs are the nodes proving that
## the `key` path does not exist.
##
## Note that the function always returns an error unless the `accPath` is
## valid.
##
acc.setTrackNewApi AccSlotProofFn
result = block:

View File

@ -132,6 +132,11 @@ func toStr(rc: CoreDbRc[seq[Blob]]): string =
if rc.isOk: "ok([" & rc.value.mapIt("[#" & $it.len & "]").join(",") & "])"
else: "err(" & rc.error.toStr & ")"
func toStr(rc: CoreDbRc[(seq[Blob],bool)]): string =
if rc.isOk: "ok([" & rc.value[0].mapIt("[#" & $it.len & "]").join(",") &
"]," & $rc.value[1] & ")"
else: "err(" & rc.error.toStr & ")"
func toStr(rc: CoreDbRc[Hash256]): string =
if rc.isOk: "ok(" & rc.value.toStr & ")" else: "err(" & rc.error.toStr & ")"

View File

@ -25,6 +25,7 @@ import
type
ProofData = ref object
chain: seq[Blob]
missing: bool
error: AristoError
hike: Hike
@ -122,6 +123,14 @@ func asExtension(b: Blob; path: Hash256): Blob =
else:
b
when false:
# just keep for potential debugging
proc sq(s: string): string =
## For long strings print `begin..end` only
let n = (s.len + 1) div 2
result = if s.len < 20: s else: s[0 .. 5] & ".." & s[s.len-8 .. ^1]
result &= "[" & (if 0 < n: "#" & $n else: "") & "]"
# ------------------------------------------------------------------------------
# Private test functions
# ------------------------------------------------------------------------------
@ -152,13 +161,33 @@ proc testCreatePortalProof(node: JsonNode, testStatusIMPL: var TestStatus) =
# Create proof chains
for (path,proof) in sample.pairs:
let rc = ps.db.partAccountTwig path
check rc.isOk == (proof.error == AristoError 0)
if rc.isOk:
proof.chain = rc.value
if proof.error == AristoError(0):
check rc.isOk and rc.value[1] == true
proof.chain = rc.value[0]
elif proof.error != HikeBranchMissingEdge:
# Note that this is a partial data base and in this case the proof for a
# non-existing entry might not work properly when the vertex is missing.
check rc.isOk and rc.value[1] == false
proof.chain = rc.value[0]
proof.missing = true
# Verify proof chains
for (path,proof) in sample.pairs:
if proof.error == AristoError 0:
if proof.missing:
# Proof for missing entries
let
rVid = proof.hike.root
root = ps.db.getKey((rVid,rVid)).to(Hash256)
chain = proof.chain
block:
let rc = proof.chain.partUntwigPath(root, path)
check rc.isOk and rc.value.isNone
# Just for completeness (same a above combined into a single function)
check proof.chain.partUntwigPathOk(root, path, Opt.none Blob).isOk
elif proof.error == AristoError 0:
let
rVid = proof.hike.root
pyl = proof.hike.legs[^1].wp.vtx.lData.payloadAsBlob(ps)
@ -174,9 +203,8 @@ proc testCreatePortalProof(node: JsonNode, testStatusIMPL: var TestStatus) =
# Create the same proof again which must result into the same as before
block:
let rc = pq.db.partAccountTwig path
check rc.isOk
if rc.isOk:
check rc.value == proof.chain
if rc.isOk and rc.value[1] == true:
check rc.value[0] == proof.chain
# Verify proof
let root = pq.db.getKey((rVid,rVid)).to(Hash256)
@ -184,10 +212,10 @@ proc testCreatePortalProof(node: JsonNode, testStatusIMPL: var TestStatus) =
let rc = proof.chain.partUntwigPath(root, path)
check rc.isOk
if rc.isOk:
check rc.value == pyl
check rc.value == Opt.some(pyl)
# Just for completeness (same a above combined into a single function)
check proof.chain.partUntwigPathOk(root, path, pyl).isOk
check proof.chain.partUntwigPathOk(root, path, Opt.some pyl).isOk
# Extension nodes are rare, so there is one created, inserted and the
# previous test repeated.
@ -204,18 +232,18 @@ proc testCreatePortalProof(node: JsonNode, testStatusIMPL: var TestStatus) =
# Re-create proof again
block:
let rc = pq.db.partAccountTwig path
check rc.isOk
if rc.isOk:
check rc.value == chain
check rc.isOk and rc.value[1] == true
if rc.isOk and rc.value[1] == true:
check rc.value[0] == chain
let root = pq.db.getKey((rVid,rVid)).to(Hash256)
block:
let rc = chain.partUntwigPath(root, path)
check rc.isOk
if rc.isOk:
check rc.value == pyl
check rc.value == Opt.some(pyl)
check chain.partUntwigPathOk(root, path, pyl).isOk
check chain.partUntwigPathOk(root, path, Opt.some pyl).isOk
# ------------------------------------------------------------------------------
# Test