-
Notifications
You must be signed in to change notification settings - Fork 684
Pass signatures of vote extensions in RequestPrepareProposal
#489
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
Pass signatures of vote extensions in RequestPrepareProposal
#489
Conversation
"github.com/cometbft/cometbft/libs/log" | ||
"github.com/cometbft/cometbft/libs/protoio" | ||
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" |
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.
Question to reviewers: Is it OK to import the core CometBFT proto types here? I'm really skeptic about that. I'm tempted to reshuffle the proto definitions to have what we need in ABCI protos.
Opinions?
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.
Doesn't really matter very much right now, I think.
With the reorg that @mzabaluev will be doing, this will end up changing anyways and will be more consistent across all code that imports our generated Protobuf code.
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.
Actually, I just did a quick search on the SDK repo and it's used in production code, even some protobufs import them (e.g., query, block, and evidence proto definitions).
So we should be good on e2e app.
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 minor nit, but otherwise seems good to me!
I'll approve once the SDK team's happy that the solution works for them.
test/e2e/app/app.go
Outdated
cfg *Config | ||
restoreSnapshot *abci.Snapshot | ||
restoreChunks [][]byte | ||
chanID string |
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.
chainID
?
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.
Good catch!
test/e2e/app/app.go
Outdated
if !ok { | ||
panic("PrepareProposal: received vote from unknown validator") | ||
} | ||
|
||
if !pub.VerifySignature(extSignBytes, vote.ExtensionSignature) { | ||
panic("PrepareProposal: received vote with invalid signature") | ||
} |
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 would the recommended behaviour be for correct applications here? I assume it's not to panic, right? (Just asking preemptively for app developers' sake - I can see how this is useful for picking up problems in our E2E tests)
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.
So, my understanding is that the signature we're verifying is local (it comes from local CometBFT, which is supposed to have verified it itself when it received the info). A real app could even skip this verification in PrepareProposal
under the argument it trusts that CometBFT has verified it.
The missing piece of this PR (hence it's still a draft) is shipping all necessary info into special transactions so that ProcessProposal
can also verify (which is where a real app would verify). In that case it wouldn't be a panic, but a ResponseProcessProposal
with reject
.
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.
In the case of the app, which verifies vote exts "injected" by the proposer, if any such verification fails, rather if more than 2/3 voting power fails, then it would REJECT the proposal.
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 didn't get this last comment. The proposal contains extensions from 2/3 of the voting power from the previous round, out of which 1/3+1 is guaranteed to be from correct validators. What do you mean by "if more than 2/3 voting power fails"?
Maybe I am not understanding how you plan on using it. Is there anything you could share?
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 proposal contains extensions from 2/3 of the voting power from the previous round, out of which 1/3+1 is guaranteed to be from correct validators.
Yes, given to the proposer during PrepareProposal
. The remaining validators that receive the manually injected vote extensions during ProcessProposal
have to ensure they get at least 2/3 from the proposer.
@@ -487,12 +487,14 @@ func buildExtendedCommitInfo(ec *types.ExtendedCommit, store Store, initialHeigh | |||
panic(fmt.Errorf("commit at height %d received with missing vote extensions data", ec.Height)) | |||
} | |||
ext = ecs.Extension | |||
extSig = ecs.ExtensionSignature |
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.
in line 434, above, Tendermint -> CometBFT
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.
Thanks for spotting. I'm changing this in an upcoming related PR, which I am doing separately to avoid bloating this one.
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.
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 :)
Thanks for also providing an example in the tests for how to perform verification app-side 👍
@@ -444,6 +444,8 @@ message ExtendedVoteInfo { | |||
bool signed_last_block = 2; | |||
// Non-deterministic extension provided by the sending validator's application. | |||
bytes vote_extension = 3; |
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.
would calling it just extension
cause confusion?
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 prefer to err on the safe side (long but clear name), unless someone has a strong opinion on this
@@ -444,6 +444,8 @@ message ExtendedVoteInfo { | |||
bool signed_last_block = 2; | |||
// Non-deterministic extension provided by the sending validator's application. | |||
bytes vote_extension = 3; | |||
// Vote extension signature created by CometBFT | |||
bytes extension_signature = 4; |
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.
and just signature
?
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.
Not possible, we shouldn't confuse it with the signature of the vote itself
// transaction that attempts to modify the "extensionSum" value. | ||
txs := make([][]byte, len(req.Txs)+1) | ||
for i, tx := range req.Txs { | ||
extCommitBytes, err := req.LocalLastCommit.Marshal() |
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.
As this serves as an example app, this whole loop could be applied irrespective of extCount>0
to guard against transactions starting with extTxPrefix
and reservedKey
.
Then build and append the special transaction only if extCount>0
.
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 like your suggestion, let me see what I can do.
Also, for the future, we can strengthen the logic of this function, because we do know when vote extensions are active and when they are not. The way it is now is a bit loose, and, since this is a testing app we should probable make if panic if we receive extensions when we shouldn't (or if they are missing when they shouldn't)
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.
Actually, I decided to go for the "tightening up" the checks we do on vote extensions. Everything is pretty optional in this PR. I have an upcoming PR lined up which does that, and simplifies this loop massively. Please take a look when it's up
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.
"github.com/cometbft/cometbft/version" | ||
) | ||
|
||
const ( | ||
appVersion = 1 | ||
voteExtensionKey string = "extensionSum" | ||
voteExtensionMaxVal int64 = 128 | ||
reservedKey string = "reservedTxKey" |
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.
Eventhough addresses do not contain y
, it might be a good idea to add a separator to the end of the key.
reservedKey string = "reservedTxKey" | |
reservedKey string = "reservedTxKey" |
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.
Yeah, good idea
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.
Also, coming in the next PR
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.
…gn-prepareproposal
…sign-prepareproposal
…gn-prepareproposal
* Move `apphash`from `Commit`to `FinalizeBlock` (leftovers) (#478) * [partial cherry-pick] abci: Move `app_hash` parameter from `Commit` to `FinalizeBlock` (#8664) * Removed from proto * make proto-gen * make build works * make some tests pass * Fix TestMempoolTxConcurrentWithCommit * Minor change * Update abci/types/types.go * Update internal/state/execution.go * Update test/e2e/app/state.go Co-authored-by: Callum Waters <cmwaters19@gmail.com> * Updated changelog and `UPGRADING.md` * Fixed abci-cli tests, and doc * Addressed @cmwaters' comments * Addressed @cmwaters' comments, part 2 Co-authored-by: Callum Waters <cmwaters19@gmail.com> * Levftover typo in spec --------- Co-authored-by: Callum Waters <cmwaters19@gmail.com> * Compare `types.proto` and `feature/abci++vef` and tip of `v0.36.x` (#488) * CometBFT renaming in types.proto * AppHash * Make proto-gen * Trying 1.20.2 explicitly * Votes can't have zero height (#511) * [partial cherry-pick] e2e: programmable ABCI method times (#8638) (#515) * e2e: programmable ABCI method times * fix linting error Co-authored-by: Callum Waters <cmwaters19@gmail.com> * Pass signatures of vote extensions in `RequestPrepareProposal` (#489) * Pass vote extension signature in `PrepareProposal` * make proto-gen * Verify extensions at PrepareProposal * Addressed @thanethomson's comments * Fix vote extension activation in e2e * ProcessProposal: 1st try * Working.... * Refactoring * Verify signatrue in unit test * spacing * Addressed @lasarojc's comments * Fix test --------- Co-authored-by: Callum Waters <cmwaters19@gmail.com>
This PR is missing a changelog entry. And it is a breaking change. |
Ref #4458. |
Contributes to #485
(please also see second part of the changes in #509)
Contents of this PR:
PrepareProposal
PrepareProposal
so that it is also available atProcessProposal
PrepareProposal
which panics if something is unexpected, as the data comes from the local CometBFT, which should have validated everything itselfProcessProposal
which, rather than panicking, returns reject as the data cannot have come from a correct node (which, in this example, would have panicked while doing the same verification)Note: The checks on
PrepareProposal
are done here because e2e app is for testing purposes, and also to demonstrate how to do it. However, real applications will probably be OK with skipping verification atPrepareProposal
and thus trusting the local CometBFT. In that casePrepareProposal
would just be forwarding the information in TXs toProcessProposal
(similarly to how it is done in this PR), and havingProcessProposal
verify the extensions and signatures.PR checklist
.changelog
(we use unclog to manage our changelog)docs/
orspec/
) and code comments