Sneaky Mercurial Python
High
ExtendVote()
will return early without extending any vote when called in the FirstBlockHeight
of a new epoch
The same issue affects the VerifyVoteExtension()
function too.
When vote_ext::ExtendVote()
is called, it checks to ensure that the request is from the last or most recent processed height of the current epoch
File: ...babylon/x/checkpointing/vote_ext.go
42: func (h *VoteExtensionHandler) ExtendVote() sdk.ExtendVoteHandler {
43: return func(ctx sdk.Context, req *abci.RequestExtendVote) (*abci.ResponseExtendVote, error) {
44: k := h.ckptKeeper
45: @> // the returned response MUST not be nil
46: emptyRes := &abci.ResponseExtendVote{VoteExtension: []byte{}}
47:
48: epoch := k.GetEpoch(ctx)
49: // BLS vote extension is only applied at the last block of the current epoch
50: @> if !epoch.IsLastBlockByHeight(req.Height) {
51: return emptyRes, nil
52: }
This is to ensure votes are not extended from far previous blocks
However, there is a problem with this, if we look into the epoch.IsLastBlockByHeight()
function, notice that it wrongly assumes an epoch will always have a last block (this is not always the case)
File: ...babylon/x/epoching/types/epoching.go
52: func (e Epoch) IsLastBlockByHeight(height int64) bool {
53: @> return e.GetLastBlockHeight() == uint64(height)
54: }
File: .../babylon/x/epoching/types/epoching.go
30: func (e Epoch) GetLastBlockHeight() uint64 {
31: if e.EpochNumber == 0 {
32: return 0
33: }
34: @> return e.FirstBlockHeight + e.CurrentEpochInterval - 1
35: }
Notice that the GetLastBlockHeight()
function does not take into consideration when the epoch is in its FirstBlockHeight
vote is being extended when the epoch is in its FirstBlockHeight
NIL
NIL
- Vote Extension will be blocked and no vote is extended (i.e empty vote extension is sent)
- proposal preparation is also blocked because votes are not extended and this can lead to a chain halt
- Votes cannot be verified too
- current
epoch
= 5 CurrentEpochInterval
= 20FirstBlockHeight
= 101- current block height =
FirstBlockHeight
= 101 req.Height
= 100ExtendVote()
is called ande.GetLastBlockHeight()
is evaluated as
34: return e.FirstBlockHeight + e.CurrentEpochInterval - 1
34: 101 + 20 -1 = 120
- the condition below evaluates to false and
ExtendVote()
returns an empty byte slice foremptyRes
and no vote is extended
53: @> return e.GetLastBlockHeight() == uint64(height) // 120 != 100
- Consider modifying the
ExtendVote()
function to allow extension from the last block of the previous epoch when the a new epoch is in itsFirstBlockHeight
. - When the
epoch
is in itsFirstBlockHeight
,GetLastBlockHeight()
should return
FirstBlockHeight - 1