-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
docs: ADR-064 ABCI++ (2.0) #14657
docs: ADR-064 ABCI++ (2.0) #14657
Conversation
So prior to documenting anything for vote extensions, I figured it would be best to discuss various approaches more broadly first, since vote extensions are the most interesting surface area of ABCI 2.0 and we should nail the API for best dev UX. What I currently have in mind is something of the following:
These ^ should be pretty much be a given and not contentious. What the more interesting and trickier part will be is how to handle the state of vote extensions and ensuring state ends up where and when it needs to. Specifically, I'm thinking:
Assuming a proposal is accepted, the vote extensions for that block will be made available to the proposer only at the next block, during So here is another part we need to solve. How do we make this available? One thing I can think of is encourage apps to insert a special vote extensions tx into the proposal during |
Before jumping into the details of what changes are needed for CosmosSDK to accommodate Height X
Height X+1
If the above assumptions are true, I wonder what Would you be able to double check my understanding above? |
Co-authored-by: ttl33 <[email protected]>
@ttl33 happy to answer your Qs and thanks for taking a look!
It first calls
What do you mean by bookkeeping exactly? If I understand you correctly, all the SDK is responsible for is (A) creating a vote extension, which can be non-deterministic btw, and (B) verifying other validators vote extensions.
I'll combine answers to Q3 and Q4 together. So as far as I understand, from Tendermint's PoV, vote extensions are solely used in the pre-commit voting phase and then included in This ties into the crux of the problem! How do we propogate and provide the vote extensions along with any proposer-specific-derived-data to validators in So my proposal is that apps include an SDK-provided special tx type called |
This matches my understanding!
What I meant here is: Other than providing hooks to call
I think this info is critical in determining if the proposing validator needs to explicitly put the
In example 1, it's desired that the proposer puts all
I think I have similar thoughts. More concretely, something like:
|
It does not have to correct, but I think to fully make
Vote extensions do not exist on-chain, at least not in the app-side. I don't see any way of doing this unless you send them along in special txs like I proposed. Besides,
What you're describing is a fundamental change to the Tendermint ABCI++ protocol, which is outside our domain. IIRC, @sergio-mena stated that they discussed various approaches and ultimately decided on not including I agree that if |
When i think of the of voteextensions and how it could be used i could see this:
Being an interface that a module implements if they'd like access to the feature. It would be similar to the begin/end block interfaces and be optional to applications. The handler in baseapp can be overridden but in the world of cosmwasm where a contract wants to take advantage of this, it would need to be in the module instead of in baseapp. Was that what you were mentioning? |
I see. I think the source of confusion was the fact that I thought In this case, I don't necessarily think the CosmosSDK needs to prescribe a bookkeeping mechanism (i.e. IMO, your suggestion with
I agree.
Thinking this over, it makes sense to not include the I am curious how the final |
Yes @tac0turtle but that part of the ADR is trivial, the crux of what's unsolved is how to propagate committed vote extensions to validators in
Yup! Since at X+1 they're already verified and committed.
Why? Why bring in dependencies and complexities when you can just easily keep track of it in memory? You don't need to persist anything.
Yes -- this is the tricky part and what we need to discuss and figure out. Essentially, this entire thing boils down to 2 questions:
|
Exactly. I am advocating for giving the app the flexibility to handle the the
Hmm, from our convo earlier, I was under the impression that the vote extension won't be part of For oracle prices, it's critical that there's some guarantee or validation process on the VoteExtension data is indeed from 2/3+ validators with valid signatures. But not sure if other VoteExtension usages need this guarantee. WDYT? |
I agree with this assessment and it's a good way of framing the discussion points. For question number 2, I feel it will be trickier. In addition, I'm worried that it will be extra hard to verify this information if the validator set changes in any way since the previous block. |
What do you mean? It absolutely does have use for it. During height From what I gather, we have a firm grasp on what vote extensions are. Yet, we do not have a firm grasp on how they're meant to be used, propagated effectively and validated safely. @thanethomson is scheduling a sync call with a stakeholders so we can all better understand the complete picture. Once this happens, I feel like we can come up with a good solution 😄 |
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.
Overall LGTM. Thanks for putting this together Bez!
Left some non-blocking comments and nits
|
||
### `VoteExtensions` | ||
|
||
Similarly for `PrepareProposal` and `ProcessProposal`, we propose to introduce |
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.
To clarify - are the following statements correct? If so, it would be nice to add some clarifying statements in the ADR
- For each block round, every validator node will call
ExtendVoteHandler
- For each block round and for each vote they receive from other nodes, every validator node will call
VerifyVoteExtensionHandler
So if there are X number of validators for a block height H round 1, at the network level, there will be X number of calls to 1 and up to (X-1) * X calls to 2?
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.
Of course, happy to clarify!
- Yes, for every round, CometBFT will execute an
ExtendVote
request. In fact, it does this for pretty much all ABCI methods. - Yes, this is also correct from my understanding. For each height and round within that height, CometBFT will execute a
VerifyVoteExtension
request.
Ideally, when you extend the vote and then verify all incoming vote exts, you receive 2/3+ valid ones and avoid having to proceed to the next round.
So if there are X number of validators for a block height H round 1, at the network level, there will be X number of calls to 1 and up to (X-1) * X calls to 2?
Yes, I believe there will be X calls to ExtendVote
. For VerifyVoteExtension
, the proposer just needs 2/3 pre-commits to be valid, so this number can vary, but in theory it can be up to X-1, yes.
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.
Updated to include a statement on this. LMK if you think it suffices :)
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.
sg, thanks for clarifying. feel free to resolve this comment
verification. This state management could be ephemeral or could be some form of | ||
on-disk persistence. | ||
|
||
Example: |
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.
Nice example.
Nit: Will there be helper methods available in CosmosSDK and examples implemented in the simapp
?
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.
I'm not sure if there will be any since it could be so application specific. What kind of example or basic implementation ideas did you have in mind?
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.
I think the price example is a simple but sufficient one.
For helper methods, there could be
- no-op ExtendVote/VerifyVoteHandlers.
- encoding/decoding helper methods (?) if these can be generic?
- setter (?) if these can be generic? i.e. you have
SetPrices
// a vote extension, such as fetching a series of prices for supported assets. | ||
func (h VoteExtensionHandler) ExtendVoteHandler(ctx sdk.Context, req abci.RequestExtendVote) abci.ResponseExtendVote { | ||
prices := GetPrices(ctx, h.mk.Assets()) | ||
bz, err := EncodePrices(h.cdc, prices) |
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.
For my own understanding - why does the application have to encode the data before setting the ResponseExtendVote.VoteExtension
?
Is it possible that the VoteExtension
data to be not encoded if the entire ResponseExtendVote
will be encoded by the downstream?
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.
ResponseExtendVote.VoteExtension
is an opaque byte slice from CometBFT's perspective. We have to transform real/domain data to raw data somehow.
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.
got it, thanks for clarifying. feel free to resolve this comment!
// store our vote extension at the given height | ||
// | ||
// NOTE: Vote extensions can be overridden since we can timeout in a round. | ||
SetPrices(h.state, req, bz) |
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.
Why is this needed?
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.
This is just a demo/example. I did this to demonstrate setting/saving a node's local vote ext.
Why? Because when it comes down to verifying other validators vote exts, you might want to compare theirs vs yours. E.g. in the case of oracle prices, you might want to verify that any incoming vote ext is no more than X% +/- from yours.
Of course this is just an example, and you don't have to save or keep track of anything at all.
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.
I see, this would be a good example to have
return abci.ResponseVerifyVoteExtension{Status: REJECT} | ||
} | ||
|
||
if err := ValidatePrices(h.state, req, prices); err != nil { |
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.
To confirm, there's no need to verify the vote extension signature here right? I assume that will be already taken care of by the CometBFT before this data gets to this point
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.
Correct. There is no need to verify signatures in VerifyVoteExtension
.
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.
got it, thanks for clarifying. it might be useful to leave a comment that there's no need to verify here due to the reason above
|
||
// ValidateVoteExtensions is a function that an application can execute in | ||
// ProcessProposal to verify vote extension signatures. | ||
func (app *BaseApp) ValidateVoteExtensions(ctx sdk.Context, currentHeight int64, extCommit abci.ExtendedCommitInfo) error { |
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.
To confirm - the signature verification logic stated here would be available as a helper method in CosmosSDK right? Trying to avoid the app needing to implement the same logic 😆
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.
Correct, exactly!
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.
got it, thanks for clarifying. feel free to resolve this comment!
What this means is that any explicit state changes in a `ProcessProposal` handler | ||
using the injected `processProposalState.Context` will be written to state! An | ||
application must be careful to execute `ResetProcessProposalState` where and when | ||
appropriate. |
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.
To confirm my understanding, is the following correct?
- ProcessProposalHandler: does a bunch of state modifications
- ProcessProposalHandler: if
ResetProcessProposalState
is called, then all the modifications made in 1 is reverted. If not, then all the modifications will be passed onto the next step, which isFinalizeBlock
which will update the actual state - Since
SetVoteExtResult
is called after theReset
, the VoteExtResult will be available inFinalizeBlock
state?
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.
Bingo!
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.
This was the cleanest way I could think of doing it.
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.
got it, thanks for clarifying. feel free to resolve this comment!
// | ||
// NOTE: Applications must call this to ensure no state transactions up to now | ||
// are committed! | ||
h.app.ResetProcessProposalState() |
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.
What's the reasoning for adding ResetProcessProposalState
?
Is it because we want to preserve the work done in ProcessProposal
to FinalizeBlock
in certain use cases?
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.
The reason is that generally speaking, you don't want state mutations from ProcessProposal
to be committed because you're just going to re-do those mutations during FinalizeBlock
anyway. However, there are situations where you DO want SOME mutations to commit from ProcessProposal
, those being anything to do with vote extensions.
Hence, the need or ability for some way to say "Hey, I want this to be committed but not this other stuff."
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.
LGTM, nicely written adr
will add significant performance overhead to `ProcessProposal`. Granted, the | ||
signature verification process can happen concurrently using an error group | ||
with `GOMAXPROCS` goroutines. |
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.
will add significant performance overhead to `ProcessProposal`. Granted, the | |
signature verification process can happen concurrently using an error group | |
with `GOMAXPROCS` goroutines. | |
will add significant performance overhead to `ProcessProposal`. Granted, the | |
signature verification process can take advantage of multiple different optimisations (parallelisation, batching and/or aggregation). |
minor nit
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.
LGTM 🚀
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.
One general observation after reading this ADR is that there are no provisions for upgrading from v0.47.x
(or earlier), which doesn't have vote extensions, to the new version, where vote extensions are mandatory.
At the CometBFT level, we have dealt with this both at the level of the spec and implementation. Please take a look, although the best source to get context on this is still CometBFT's RFC100.
safety purposes, we also propose that the proposer itself verify all the vote | ||
extension signatures it receives in `RequestPrepareProposal`. |
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.
Real apps shouldn't need to do this, since all those extensions are supposed to have been validated upon reception in H-1
. Not that, at this point, all this info is coming from the local CometBFT, not from the network.
is decided. | ||
|
||
In other words, `FinalizeBlock` encapsulates the current ABCI execution flow of | ||
`BeginBlock`, one or more `DeliverTx`, and `EndBlock` into a single ABCI method. |
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.
's/one or more/zero or more/'
@sergio-mena can you clarify why provisions need to be explicitly be made for upgrading from SDK v0.47.x to v0.48.x? The latter is not state compatible with the former, so a network upgrade will be needed. If an upgrade occurs at height H, there will be no vote extensions at H-1 anyway. So what explicit case(s) do we need to handle exactly? |
I read through the RFC (this part seemed relevant) and I'm not sure either what's the case. Did you get an answer @alexanderbez ? |
Yeah this is addressed in the ADR 👍 |
@alexanderbez thanks for addressing the upgrade path in #15721 |
Description
This ADR outlines the design of ABCI++ (2.0), which include
VoteExtensions
andFinalizeBlock
, in the Cosmos SDK.Ref: #12272
Author Checklist
All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.
I have...
!
to the type prefix if API or client breaking changeCHANGELOG.md
Reviewers Checklist
All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.
I have...
!
in the type prefix if API or client breaking change