···10101111 abcitypes "github.com/cometbft/cometbft/abci/types"
1212 "github.com/cometbft/cometbft/crypto/ed25519"
1313+ protocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto"
1314 "github.com/gbl08ma/stacktrace"
1415 cbornode "github.com/ipfs/go-ipld-cbor"
1516 "github.com/samber/lo"
···269270func (d *DIDPLCApplication) FinalizeBlock(ctx context.Context, req *abcitypes.RequestFinalizeBlock) (*abcitypes.ResponseFinalizeBlock, error) {
270271 defer (d.logMethod("FinalizeBlock", "height", req.Height, "hash", req.Hash))()
271272272272- markVotingParticipation := func() error {
273273- for _, vote := range req.DecidedLastCommit.Votes {
274274- err := store.Consensus.MarkValidatorVote(d.ongoingWrite, vote.GetValidator().Address, uint64(req.Height))
275275- // we expect to attempt to store votes for validators that aren't active because validator_updates take a few blocks to fully take effect
276276- if err != nil && !errors.Is(err, store.ErrValidatorNotActive) {
277277- return stacktrace.Propagate(err)
278278- }
279279- }
280280- return nil
281281- }
273273+ d.createOngoingTxIfNeeded(req.Time)
282274283283- if bytes.Equal(req.Hash, d.lastProcessedProposalHash) && d.lastProcessedProposalExecTxResults != nil {
284284- d.createOngoingTxIfNeeded(req.Time)
275275+ if !bytes.Equal(req.Hash, d.lastProcessedProposalHash) || d.lastProcessedProposalExecTxResults == nil {
276276+ // a block other than the one we processed in ProcessProposal was decided
277277+ // discard the current modified state, and process the decided block
278278+ d.DiscardChanges()
285279286286- err := markVotingParticipation()
287287- if err != nil {
288288- return nil, stacktrace.Propagate(err)
280280+ txResults := make([]*processResult, len(req.Txs))
281281+ for i, tx := range req.Txs {
282282+ var err error
283283+ txResults[i], err = processTx(ctx, d.transactionProcessorDependenciesForOngoingProcessing(true, req.Time), tx)
284284+ if err != nil {
285285+ return nil, stacktrace.Propagate(err)
286286+ }
289287 }
290288289289+ d.lastProcessedProposalHash = slices.Clone(req.Hash)
290290+ d.lastProcessedProposalExecTxResults = txResults
291291+ } else {
291292 // the block that was decided was the one we processed in ProcessProposal, and ProcessProposal processed successfully
292293 // reuse the uncommitted results
293293- return &abcitypes.ResponseFinalizeBlock{
294294- TxResults: lo.Map(d.lastProcessedProposalExecTxResults, func(result *processResult, _ int) *abcitypes.ExecTxResult {
295295- return result.ToABCI()
296296- }),
297297- ValidatorUpdates: lo.FlatMap(d.lastProcessedProposalExecTxResults, func(result *processResult, _ int) []abcitypes.ValidatorUpdate {
298298- return result.validatorUpdates
299299- }),
300300- AppHash: d.tree.WorkingHash(),
301301- }, nil
302294 }
303303- // a block other than the one we processed in ProcessProposal was decided
304304- // discard the current modified state, and process the decided block
305305- d.DiscardChanges()
306295307307- txResults := make([]*processResult, len(req.Txs))
308308- validatorUpdates := []abcitypes.ValidatorUpdate{}
309309- for i, tx := range req.Txs {
310310- var err error
311311- txResults[i], err = processTx(ctx, d.transactionProcessorDependenciesForOngoingProcessing(true, req.Time), tx)
312312- if err != nil {
313313- return nil, stacktrace.Propagate(err)
296296+ validatorUpdates, jailedValidatorPubkeys, err := d.processMisbehavior(req.Misbehavior)
297297+ if err != nil {
298298+ return nil, stacktrace.Propagate(err)
299299+ }
300300+ for _, result := range d.lastProcessedProposalExecTxResults {
301301+ for _, update := range result.validatorUpdates {
302302+ _, jailed := jailedValidatorPubkeys[[32]byte(update.PubKey.GetEd25519())]
303303+ // if this is an epoch block, ensure we don't un-jail any validators that were just jailed for misbehavior
304304+ if !jailed {
305305+ validatorUpdates = append(validatorUpdates, update)
306306+ }
314307 }
315315- validatorUpdates = append(validatorUpdates, txResults[i].validatorUpdates...)
316308 }
317309318318- d.lastProcessedProposalHash = slices.Clone(req.Hash)
319319- d.lastProcessedProposalExecTxResults = txResults
320320-321321- err := markVotingParticipation()
322322- if err != nil {
323323- return nil, stacktrace.Propagate(err)
310310+ for _, vote := range req.DecidedLastCommit.Votes {
311311+ err := store.Consensus.MarkValidatorVote(d.ongoingWrite, vote.GetValidator().Address, uint64(req.Height))
312312+ // we expect to attempt to store votes for validators that aren't active because validator_updates take a few blocks to fully take effect
313313+ if err != nil && !errors.Is(err, store.ErrValidatorNotActive) {
314314+ return nil, stacktrace.Propagate(err)
315315+ }
324316 }
325317326318 return &abcitypes.ResponseFinalizeBlock{
···330322 ValidatorUpdates: validatorUpdates,
331323 AppHash: d.tree.WorkingHash(),
332324 }, nil
325325+}
326326+327327+func (d *DIDPLCApplication) processMisbehavior(misbehavior []abcitypes.Misbehavior) ([]abcitypes.ValidatorUpdate, map[[store.PublicKeyLength]byte]struct{}, error) {
328328+ validatorUpdates := []abcitypes.ValidatorUpdate{}
329329+ jailed := make(map[[store.PublicKeyLength]byte]struct{})
330330+ for _, m := range misbehavior {
331331+ pubkey, err := store.Consensus.ActiveValidatorPubKey(d.ongoingRead, uint64(m.Height), m.Validator.Address)
332332+ if errors.Is(err, store.ErrValidatorNotActive) {
333333+ // hmm, this makes it trickier to find the pubkey for the validator
334334+ // if the validator is no longer active maybe we can just ignore this?
335335+ continue
336336+ } else if err != nil {
337337+ return nil, nil, stacktrace.Propagate(err)
338338+ }
339339+340340+ // note: the validator may receive additional penalties on the next epoch block,
341341+ // because the validator voting power is (potentially only temporarily) fully removed
342342+ // but the validator is not removed from the store.Consensus.ActiveValidatorsIterator visitees.
343343+ // its votes might not be included for the rest of this epoch, this will further count negatively towards its reputation
344344+345345+ err = store.Consensus.ChangeValidatorReputation(d.ongoingWrite, pubkey, func(reputation uint64) (uint64, error) {
346346+ switch m.Type {
347347+ case abcitypes.MisbehaviorType_DUPLICATE_VOTE:
348348+ return reputation / 10, nil
349349+ case abcitypes.MisbehaviorType_LIGHT_CLIENT_ATTACK:
350350+ return (reputation * 7) / 10, nil
351351+ default:
352352+ return 0, stacktrace.NewError("unknown misbehavior type")
353353+ }
354354+ })
355355+ if err != nil {
356356+ return nil, nil, stacktrace.Propagate(err)
357357+ }
358358+359359+ // remove validator from validator set until the _next_ epoch block
360360+ // if this is an epoch block, because misbehavior is processed after the update validators transaction, this may mean the validator will spend an entire epoch inactive
361361+ // the validator may (or may not) regain some voting power on the next epoch block
362362+ jailed[[store.PublicKeyLength]byte(pubkey)] = struct{}{}
363363+ validatorUpdates = append(validatorUpdates, abcitypes.ValidatorUpdate{
364364+ PubKey: protocrypto.PublicKey{
365365+ Sum: &protocrypto.PublicKey_Ed25519{
366366+ Ed25519: pubkey[:],
367367+ },
368368+ },
369369+ Power: int64(0),
370370+ })
371371+ }
372372+ return validatorUpdates, jailed, nil
333373}
334374335375// Commit implements [types.Application].