docs: add docs on server v2 vote extensions (#23010)
This commit is contained in:
parent
2c16372907
commit
65ceca640b
160
docs/build/abci/03-vote-extensions.md
vendored
160
docs/build/abci/03-vote-extensions.md
vendored
@ -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
|
||||
},
|
||||
))
|
||||
```
|
||||
Loading…
Reference in New Issue
Block a user