@@ -120,3 +120,163 @@ func (k Keeper) BeginBlocker(ctx context.Context) error {
120120 return nil
121121}
122122```
123+
124+ ## Vote Extensions on v2
125+
126+ ### Extend Vote
127+
128+ In v2, the ` ExtendVoteHandler ` function works in the same way as it does in v1,
129+ but the implementation is passed as a server option when calling ` cometbft.New ` .
130+
131+ ``` go
132+ serverOptions.ExtendVoteHandler = CustomExtendVoteHandler ()
133+
134+ func CustomExtendVoteHandler () handlers .ExtendVoteHandler {
135+ return func (ctx context.Context , rm store.ReaderMap , evr *v1.ExtendVoteRequest ) (*v1.ExtendVoteResponse , error ) {
136+ return &v1.ExtendVoteResponse {
137+ VoteExtension: []byte (" BTC=1234567.89;height=" + fmt.Sprint (evr.Height )),
138+ }, nil
139+ }
140+ }
141+ ```
142+
143+ ### Verify Vote Extension
144+
145+ Same as above:
146+
147+ ``` go
148+ serverOptions.VerifyVoteExtensionHandler = CustomVerifyVoteExtensionHandler ()
149+
150+ func CustomVerifyVoteExtensionHandler ]() handlers.VerifyVoteExtensionHandler {
151+ return func (context.Context , store.ReaderMap , *abci.VerifyVoteExtensionRequest ) (*abci.VerifyVoteExtensionResponse , error ) {
152+ return &abci.VerifyVoteExtensionResponse {}, nil
153+ }
154+ }
155+
156+ ```
157+
158+ ### Prepare and Process Proposal
159+
160+ These are also passed in as server options when calling ` cometbft.New ` .
161+
162+ ``` go
163+ serverOptions.PrepareProposalHandler = CustomPrepareProposal [T]()
164+ serverOptions.ProcessProposalHandler = CustomProcessProposalHandler [T]()
165+ ```
166+
167+ The PrepareProposal handler can be used to inject vote extensions into the block proposal
168+ by using the ` cometbft.RawTx ` util function, which allows passing in arbitrary bytes.
169+
170+ ``` go
171+ func CustomPrepareProposal [T transaction.Tx ]() handlers.PrepareHandler [T] {
172+ return func (ctx context.Context , app handlers.AppManager [T], codec transaction.Codec [T], req *v1.PrepareProposalRequest , chainID string ) ([]T, error ) {
173+ var txs []T
174+ for _ , tx := range req.Txs {
175+ decTx , err := codec.Decode (tx)
176+ if err != nil {
177+ continue
178+ }
179+
180+ txs = append (txs, decTx)
181+ }
182+
183+ // "Process" vote extensions (we'll just inject all votes)
184+ injectedTx , err := json.Marshal (req.LocalLastCommit )
185+ if err != nil {
186+ return nil , err
187+ }
188+
189+ // put the injected tx into the first position
190+ txs = append ([]T{cometbft.RawTx (injectedTx).(T)}, txs...)
191+
192+ return txs, nil
193+ }
194+ }
195+ ```
196+
197+ The ProcessProposal handler can be used to recover the vote extensions from the first transaction
198+ and perform any necessary verification on them. In the example below we also use the
199+ ` cometbft.ValidateVoteExtensions ` util to verify the signature of the vote extensions;
200+ this function takes a "validatorStore" function that returns the public key of a validator
201+ given its consensus address. In the example we use the default staking module to get the
202+ validators.
203+
204+ ``` go
205+ func CustomProcessProposalHandler [T transaction.Tx ]() handlers.ProcessHandler [T] {
206+ return func (ctx context.Context , am handlers.AppManager [T], c transaction.Codec [T], req *v1.ProcessProposalRequest , chainID string ) error {
207+ // Get all vote extensions from the first tx
208+
209+ injectedTx := req.Txs [0 ]
210+ var voteExts v1.ExtendedCommitInfo
211+ if err := json.Unmarshal (injectedTx, &voteExts); err != nil {
212+ return err
213+ }
214+
215+ // Get validators from the staking module
216+ res , err := am.Query (
217+ ctx,
218+ 0 ,
219+ &staking.QueryValidatorsRequest {},
220+ )
221+ if err != nil {
222+ return err
223+ }
224+
225+ validatorsResponse := res.(*staking.QueryValidatorsResponse )
226+ consAddrToPubkey := map [string ]cryptotypes.PubKey {}
227+
228+ for _ , val := range validatorsResponse.GetValidators () {
229+ cv := val.ConsensusPubkey .GetCachedValue ()
230+ if cv == nil {
231+ return fmt.Errorf (" public key cached value is nil" )
232+ }
233+
234+ cpk , ok := cv.(cryptotypes.PubKey )
235+ if ok {
236+ consAddrToPubkey[string (cpk.Address ().Bytes ())] = cpk
237+ } else {
238+ return fmt.Errorf (" invalid public key type" )
239+ }
240+ }
241+
242+ // First verify that the vote extensions injected by the proposer are correct
243+ if err := cometbft.ValidateVoteExtensions (
244+ ctx,
245+ am,
246+ chainID,
247+ func (ctx context.Context , b []byte ) (cryptotypes.PubKey , error ) {
248+ if _ , ok := consAddrToPubkey[string (b)]; !ok {
249+ return nil , fmt.Errorf (" validator not found" )
250+ }
251+ return consAddrToPubkey[string (b)], nil
252+ },
253+ voteExts,
254+ req.Height ,
255+ &req.ProposedLastCommit ,
256+ ); err != nil {
257+ return err
258+ }
259+
260+ // TODO: do something with the vote extensions
261+
262+ return nil
263+ }
264+ }
265+ ```
266+
267+
268+ ### Preblocker
269+
270+ In v2, the ` PreBlocker ` function works in the same way as it does in v1. However, it is
271+ is now passed in as an option to ` appbuilder.Build ` .
272+
273+ ``` go
274+ app.App , err = appBuilder.Build (runtime.AppBuilderWithPreblocker (
275+ func (ctx context .Context , txs []T ) error {
276+ // to recover the vote extension use
277+ voteExtBz := txs[0 ].Bytes ()
278+ err := doSomethingWithVoteExt (voteExtBz)
279+ return err
280+ },
281+ ))
282+ ```
0 commit comments