Tracking issue for transaction validation. Transaction validation checks should be implemented as a sequence of stateless checks that generate constraints if successful (semantic validation); these constraints are then checked as part of contextual verification when a block is being committed to the state.
See also #428 which tracks block validation.
zebra-script interface to FFI crate with zebra-chain types #708 [ ] Transaction verifier service: https://github.com/ZcashFoundation/zebra/pull/1100
[ ] Other consensus rules ??? - https://github.com/ZcashFoundation/zebra/issues?q=is%3Aissue++label%3Aconsensus+
We need #180; adding it to the list.
Some notes on what our verification architecture could look like below. First, some general desiderata:
Here's a sketch of how we could try to accomplish this. At a high level, we keep all verification logic inside of zebra-consensus, and implement each type of verification as a tower::Service. Then in zebra-chain we have aggregations of data to be verified (blocks aggregate transactions, which aggregate various signatures and proofs, etc.), and in zebra-consensus we have aggregations of verifier Services (a block verifier has a transaction verifier, which has verifiers for various signatures and proofs, etc.).
This lets us do pervasive batching using the strategy in the blog post, and importantly, it allows us to have one implementation independent of the batch size. As an example, consider a version 4 (Sapling) transaction, which can have multiple SpendDescriptions and OutputDescriptions, each with their own Groth16Proof. Depending on the context, multiple levels of batching are possible:
Suppose we have a Groth16Verifier: Service performing batch verification, with a similar API as ed25519_zebra::batch, and that the TransactionVerifier: Service aggregates Services for each component (Groth16Verifier, RedJubjubVerifier, etc). Now, if the verification services for the components are passed into the TransactionVerifier constructor, we can get each of these levels of batching with a single verification codepath, by constructing the verification services at the highest level of batching and passing them down through various constructors.
This handles batching. To handle parallelism, we probably want to ensure that as much of the work as possible is in the response future itself, rather than in the call impl, and lean on task spawning to have the runtime drive execution of futures in parallel. We will also need to be careful to spawn blocking work appropriately to not block the runtime.
I think that the futures-based approach could also work well for handling checkpointing, with the caveat that we may need to have many intermediate checkpoints. When doing checkpointing, we have an externally-trusted (h, hash): (BlockHeight, BlockHeaderHash) pair, and the chain is valid up to the checkpoint if the block at height h has hash hash (implying that all previous blocks were valid). This is structurally quite similar to batch verification, but with less work involved. Using futures allows us to avoid maintaining intermediate unverified data by having a CheckpointVerifier whose response futures resolve when the checkpoint is reached. However, this means keeping all of the blocks up to the checkpoint in memory as futures, so we would need to ship a list of checkpoints, say every 500 or 1000 blocks or so.
A nice upside of this approach would be that we could optionally extend it past Sapling activation. This would involve having a hardcoded list of "mandatory" checkpoints (up to Sapling activation), and a way to configure a file or something with other checkpoints. To me this is P-Lo but would be relatively little extra work when we got around to it.
I think those ideas touch on most of the desiderata, except:
Contextual verification (involving chain state) should be separated from semantic verification (e.g., checking proofs) as much as possible.
I think there are two next steps:
Write down explicitly all of the chain state required to validate transactions. That description could be a starting point for a Request/Response API for the zebra-storage API #136.
Go through the protocol spec and write down explicitly a complete list of all of the verification checks we need to do (filling out the checklist above). These could then turn into verification services in zebra-consensus.
WDYT @dconnolly @gtank ?
Basically this will be complete when https://github.com/ZcashFoundation/zebra/milestone/2 is complete.
Contextual verification (involving chain state) should be separated from semantic verification (e.g., checking proofs) as much as possible.
I think there are two next steps:
1. Write down explicitly all of the chain state required to validate transactions. That description could be a starting point for a `Request`/`Response` API for the `zebra-storage` API #136. 2. Go through the protocol spec and write down explicitly a complete list of all of the verification checks we need to do (filling out the checklist above). These could then turn into verification services in `zebra-consensus`.
I think there are a few different levels of context:
I'll see how I go with splitting checks into these categories in #428.
I think this might have been closed by mistake (cc @dconnolly )? (Please feel free to re-close if it wasn't)
OOF very mistake, my bad
Most helpful comment
I think those ideas touch on most of the desiderata, except:
I think there are two next steps:
Write down explicitly all of the chain state required to validate transactions. That description could be a starting point for a
Request/ResponseAPI for thezebra-storageAPI #136.Go through the protocol spec and write down explicitly a complete list of all of the verification checks we need to do (filling out the checklist above). These could then turn into verification services in
zebra-consensus.WDYT @dconnolly @gtank ?