docs: add docs on server v2 vote extensions (#23010)

This commit is contained in:
Facundo Medica 2024-12-19 20:30:47 +01:00 committed by GitHub
parent 2c16372907
commit 65ceca640b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -120,3 +120,163 @@ func (k Keeper) BeginBlocker(ctx context.Context) error {
return nil
}
```
## Vote Extensions on v2
### Extend Vote
In v2, the `ExtendVoteHandler` function works in the same way as it does in v1,
but the implementation is passed as a server option when calling `cometbft.New`.
```go
serverOptions.ExtendVoteHandler = CustomExtendVoteHandler()
func CustomExtendVoteHandler() handlers.ExtendVoteHandler {
return func(ctx context.Context, rm store.ReaderMap, evr *v1.ExtendVoteRequest) (*v1.ExtendVoteResponse, error) {
return &v1.ExtendVoteResponse{
VoteExtension: []byte("BTC=1234567.89;height=" + fmt.Sprint(evr.Height)),
}, nil
}
}
```
### Verify Vote Extension
Same as above:
```go
serverOptions.VerifyVoteExtensionHandler = CustomVerifyVoteExtensionHandler()
func CustomVerifyVoteExtensionHandler]() handlers.VerifyVoteExtensionHandler {
return func(context.Context, store.ReaderMap, *abci.VerifyVoteExtensionRequest) (*abci.VerifyVoteExtensionResponse, error) {
return &abci.VerifyVoteExtensionResponse{}, nil
}
}
```
### Prepare and Process Proposal
These are also passed in as server options when calling `cometbft.New`.
```go
serverOptions.PrepareProposalHandler = CustomPrepareProposal[T]()
serverOptions.ProcessProposalHandler = CustomProcessProposalHandler[T]()
```
The PrepareProposal handler can be used to inject vote extensions into the block proposal
by using the `cometbft.RawTx` util function, which allows passing in arbitrary bytes.
```go
func CustomPrepareProposal[T transaction.Tx]() handlers.PrepareHandler[T] {
return func(ctx context.Context, app handlers.AppManager[T], codec transaction.Codec[T], req *v1.PrepareProposalRequest, chainID string) ([]T, error) {
var txs []T
for _, tx := range req.Txs {
decTx, err := codec.Decode(tx)
if err != nil {
continue
}
txs = append(txs, decTx)
}
// "Process" vote extensions (we'll just inject all votes)
injectedTx, err := json.Marshal(req.LocalLastCommit)
if err != nil {
return nil, err
}
// put the injected tx into the first position
txs = append([]T{cometbft.RawTx(injectedTx).(T)}, txs...)
return txs, nil
}
}
```
The ProcessProposal handler can be used to recover the vote extensions from the first transaction
and perform any necessary verification on them. In the example below we also use the
`cometbft.ValidateVoteExtensions` util to verify the signature of the vote extensions;
this function takes a "validatorStore" function that returns the public key of a validator
given its consensus address. In the example we use the default staking module to get the
validators.
```go
func CustomProcessProposalHandler[T transaction.Tx]() handlers.ProcessHandler[T] {
return func(ctx context.Context, am handlers.AppManager[T], c transaction.Codec[T], req *v1.ProcessProposalRequest, chainID string) error {
// Get all vote extensions from the first tx
injectedTx := req.Txs[0]
var voteExts v1.ExtendedCommitInfo
if err := json.Unmarshal(injectedTx, &voteExts); err != nil {
return err
}
// Get validators from the staking module
res, err := am.Query(
ctx,
0,
&staking.QueryValidatorsRequest{},
)
if err != nil {
return err
}
validatorsResponse := res.(*staking.QueryValidatorsResponse)
consAddrToPubkey := map[string]cryptotypes.PubKey{}
for _, val := range validatorsResponse.GetValidators() {
cv := val.ConsensusPubkey.GetCachedValue()
if cv == nil {
return fmt.Errorf("public key cached value is nil")
}
cpk, ok := cv.(cryptotypes.PubKey)
if ok {
consAddrToPubkey[string(cpk.Address().Bytes())] = cpk
} else {
return fmt.Errorf("invalid public key type")
}
}
// First verify that the vote extensions injected by the proposer are correct
if err := cometbft.ValidateVoteExtensions(
ctx,
am,
chainID,
func(ctx context.Context, b []byte) (cryptotypes.PubKey, error) {
if _, ok := consAddrToPubkey[string(b)]; !ok {
return nil, fmt.Errorf("validator not found")
}
return consAddrToPubkey[string(b)], nil
},
voteExts,
req.Height,
&req.ProposedLastCommit,
); err != nil {
return err
}
// TODO: do something with the vote extensions
return nil
}
}
```
### Preblocker
In v2, the `PreBlocker` function works in the same way as it does in v1. However, it is
is now passed in as an option to `appbuilder.Build`.
```go
app.App, err = appBuilder.Build(runtime.AppBuilderWithPreblocker(
func(ctx context.Context, txs []T) error {
// to recover the vote extension use
voteExtBz := txs[0].Bytes()
err := doSomethingWithVoteExt(voteExtBz)
return err
},
))
```