Skip to content

Commit e9b9bce

Browse files
authored
refactor: add a new buffer blocks param to delay deletion of pending ballots (#3553)
* add buffer blocks for pending ballots * add comments and refactor BuildRewardsDistribution function. * add changelog * update comments * update comments * update comments * explicitly set updated params * add additional checks for migration * refactor to single ClearFinalizedMaturedBallots with forceDeleteAllBallots flag * remove SecondsToBlock function * add pendingBufferBlocks to proposal * format files
1 parent 760c8bb commit e9b9bce

File tree

29 files changed

+818
-325
lines changed

29 files changed

+818
-325
lines changed

changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
* [3381](https://github.com/zeta-chain/node/pull/3381) - split Bitcoin observer and signer into small files and organize outbound logic into reusable/testable functions; renaming, type unification, etc.
3434
* [3496](https://github.com/zeta-chain/node/pull/3496) - zetaclient uses `ConfirmationParams` instead of old `ConfirmationCount`; use block ranged based observation for btc and evm chain.
3535
* [3594](https://github.com/zeta-chain/node/pull/3594) - set outbound hash in cctx when adding outbound tracker
36+
* [3553](https://github.com/zeta-chain/node/pull/3553) — add a new buffer blocks param to delay deletion of pending ballots
3637

3738
### Fixes
3839

contrib/localnet/orchestrator/proposals_e2e_start/emissions_params.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@
88
"tssSignerEmissionPercentage": "0.25",
99
"observerSlashAmount": "100000000000000000",
1010
"ballotMaturityBlocks": 101,
11-
"blockRewardAmount": "9620949074074074074.074070733466756687"
11+
"blockRewardAmount": "9620949074074074074.074070733466756687",
12+
"pendingBallotsDeletionBufferBlocks": 145222
1213
},
1314
"authority": "zeta10d07y265gmmuvt4z0w9aw880jnsr700jvxasvr"
1415
}
1516
],
1617
"metadata": "test proposal",
1718
"deposit": "100000000azeta",
1819
"title": "emissions param change",
19-
"summary": "emissions param change"
20+
"summary": "emissions param change",
21+
"min_version": "v29.0.0"
2022
}

docs/openapi/openapi.swagger.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58146,6 +58146,9 @@ definitions:
5814658146
format: int64
5814758147
blockRewardAmount:
5814858148
type: string
58149+
pendingBallotsDeletionBufferBlocks:
58150+
type: string
58151+
format: int64
5814958152
title: |-
5815058153
Params defines the parameters for the module.
5815158154
Sample values:
@@ -58155,6 +58158,7 @@ definitions:
5815558158
ObserverSlashAmount: 100000000000000000,
5815658159
BallotMaturityBlocks: 100,
5815758160
BlockRewardAmount: 9620949074074074074.074070733466756687,
58161+
PendingBallotsDeletionBufferBlocks: 144000
5815858162
zetachain.zetacore.emissions.QueryListPoolAddressesResponse:
5815958163
type: object
5816058164
properties:

e2e/runner/gov_proposals.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
1616

1717
"github.com/zeta-chain/node/e2e/txserver"
18+
"github.com/zeta-chain/node/e2e/utils"
1819
)
1920

2021
// ExecuteProposalSequence defines the sequence of proposals execution during the e2e tests
@@ -74,7 +75,15 @@ func (r *E2ERunner) CreateGovProposals(sequence ExecuteProposalSequence) error {
7475
if err != nil {
7576
return fmt.Errorf("failed to parse proposal file %s: %w", file.Name(), err)
7677
}
77-
r.Logger.Print("executing proposal: file name: %s title: %s", file.Name(), parsedProposal.Title)
78+
r.Logger.Info("executing proposal: file name: %s title: %s", file.Name(), parsedProposal.Title)
79+
80+
minVersion := parsedProposal.MinVersion
81+
zetacoredVersion := r.GetZetacoredVersion()
82+
83+
if !utils.MinimumVersionCheck(minVersion, zetacoredVersion) {
84+
r.Logger.Print("⚠️ skipping proposal - %s (minimum version %s)", parsedProposal.Title, minVersion)
85+
continue
86+
}
7887

7988
// Create the proposal message
8089
msg, err := govv1.NewMsgSubmitProposal(
@@ -151,12 +160,13 @@ func voteGovProposals(
151160

152161
type proposal struct {
153162
// Msgs defines an array of sdk.Msgs proto-JSON-encoded as Anys.
154-
Messages []json.RawMessage `json:"messages,omitempty"`
155-
Metadata string `json:"metadata"`
156-
Deposit string `json:"deposit"`
157-
Title string `json:"title"`
158-
Summary string `json:"summary"`
159-
Expedited bool `json:"expedited"`
163+
Messages []json.RawMessage `json:"messages,omitempty"`
164+
Metadata string `json:"metadata"`
165+
Deposit string `json:"deposit"`
166+
Title string `json:"title"`
167+
Summary string `json:"summary"`
168+
Expedited bool `json:"expedited"`
169+
MinVersion string `json:"min_version,omitempty"`
160170
}
161171

162172
// parseSubmitProposal reads and parses the proposal.

e2e/runner/run.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,15 @@ import (
44
"fmt"
55
"time"
66

7-
"golang.org/x/mod/semver"
7+
"github.com/zeta-chain/node/e2e/utils"
88
)
99

1010
// RunE2ETests runs a list of e2e tests
1111
func (r *E2ERunner) RunE2ETests(e2eTests []E2ETest) (err error) {
1212
zetacoredVersion := r.GetZetacoredVersion()
1313
for _, e2eTest := range e2eTests {
14-
if semver.Major(zetacoredVersion) != "v0" && semver.Compare(zetacoredVersion, e2eTest.MinimumVersion) < 0 {
15-
// note: spacing is padded to width of completed message
16-
r.Logger.Print("⚠️ skipping - %s (minimum version %s)", e2eTest.Name, e2eTest.MinimumVersion)
14+
if !utils.MinimumVersionCheck(e2eTest.MinimumVersion, zetacoredVersion) {
15+
r.Logger.Print("⚠️ skipping test - %s (minimum version %s)", e2eTest.Name, e2eTest.MinimumVersion)
1716
continue
1817
}
1918
if err := r.Ctx.Err(); err != nil {

e2e/utils/utils.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/btcsuite/btcd/btcutil"
88
"github.com/btcsuite/btcd/chaincfg"
99
"github.com/stretchr/testify/require"
10+
"golang.org/x/mod/semver"
1011
)
1112

1213
// ScriptPKToAddress is a hex string for P2WPKH script
@@ -45,3 +46,13 @@ func TestingFromContext(ctx context.Context) require.TestingT {
4546

4647
return t
4748
}
49+
50+
func MinimumVersionCheck(testVersion, zetacoredVersion string) bool {
51+
// If major version is "v0", return true regardless of comparison
52+
if semver.Major(zetacoredVersion) == "v0" {
53+
return true
54+
}
55+
56+
// Otherwise, return true if zetacoredVersion >= testVersion
57+
return semver.Compare(zetacoredVersion, testVersion) >= 0
58+
}

proto/zetachain/zetacore/emissions/params.proto

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ option go_package = "github.com/zeta-chain/node/x/emissions/types";
1313
// ObserverSlashAmount: 100000000000000000,
1414
// BallotMaturityBlocks: 100,
1515
// BlockRewardAmount: 9620949074074074074.074070733466756687,
16+
// PendingBallotsDeletionBufferBlocks: 144000
1617
message Params {
1718
option (gogoproto.goproto_stringer) = false;
1819
string validator_emission_percentage = 5;
@@ -27,6 +28,7 @@ message Params {
2728
(gogoproto.customtype) = "cosmossdk.io/math.LegacyDec",
2829
(gogoproto.nullable) = false
2930
];
31+
int64 pending_ballots_deletion_buffer_blocks = 12;
3032

3133
// not used. do not edit.
3234
reserved 1 to 4;

testutil/keeper/mocks/emissions/observer.go

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

typescript/zetachain/zetacore/emissions/params_pb.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { Message, proto3 } from "@bufbuild/protobuf";
1515
* ObserverSlashAmount: 100000000000000000,
1616
* BallotMaturityBlocks: 100,
1717
* BlockRewardAmount: 9620949074074074074.074070733466756687,
18+
* PendingBallotsDeletionBufferBlocks: 144000
1819
*
1920
* @generated from message zetachain.zetacore.emissions.Params
2021
*/
@@ -49,6 +50,11 @@ export declare class Params extends Message<Params> {
4950
*/
5051
blockRewardAmount: string;
5152

53+
/**
54+
* @generated from field: int64 pending_ballots_deletion_buffer_blocks = 12;
55+
*/
56+
pendingBallotsDeletionBufferBlocks: bigint;
57+
5258
constructor(data?: PartialMessage<Params>);
5359

5460
static readonly runtime: typeof proto3;

x/emissions/abci.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,16 @@ func DistributeObserverRewards(
107107
emissionsKeeper keeper.Keeper,
108108
params types.Params,
109109
) error {
110+
// TODO : move the params BallotMaturityBlocks and BufferBlocksUnfinalizedBallots to the observer module
111+
// https://github.com/zeta-chain/node/issues/3550
110112
var (
111-
slashAmount = params.ObserverSlashAmount
113+
slashAmount = params.ObserverSlashAmount
114+
// Maturity blocks is used for distribution of rewards and deletion of finalized ballots
115+
// and pending ballots at the maturity height, are simply ignored
112116
maturityBlocks = params.BallotMaturityBlocks
113-
maturedBallots []string
117+
// The pendingBallotsDeletionBufferBlocks is a buffer number of blocks which is provided for pending ballots to allow them to be finalized
118+
pendingBallotsDeletionBufferBlocks = params.PendingBallotsDeletionBufferBlocks
119+
maturedBallots []string
114120
)
115121

116122
err := emissionsKeeper.GetBankKeeper().
@@ -143,8 +149,14 @@ func DistributeObserverRewards(
143149
// Processing Step 2: Emit the observer emissions
144150
keeper.EmitObserverEmissions(ctx, finalDistributionList)
145151

146-
// Processing Step 3: Delete all matured ballots and the ballot list
147-
emissionsKeeper.GetObserverKeeper().ClearMaturedBallotsAndBallotList(ctx, maturityBlocks)
152+
// Processing Step 3a: Delete all finalized ballots at `maturityBlocksForFinalizedBallots` height
153+
// This step optionally deletes the `BallotListForHeight` if all ballots are finalized and deleted
154+
emissionsKeeper.GetObserverKeeper().ClearFinalizedMaturedBallots(ctx, maturityBlocks, false)
155+
156+
// Processing Step 3b: Delete all ballots at the buffered maturity height.
157+
// This step deletes all remaining ballots and the `BallotListForHeight`.
158+
bufferedMaturityBlocks := maturityBlocks + pendingBallotsDeletionBufferBlocks
159+
emissionsKeeper.GetObserverKeeper().ClearFinalizedMaturedBallots(ctx, bufferedMaturityBlocks, true)
148160
return nil
149161
}
150162

x/emissions/abci_test.go

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,29 @@ func TestDistributeObserverRewards(t *testing.T) {
367367
slashAmount: sdkmath.NewInt(25),
368368
rewardsPerBlock: emissionstypes.BlockReward,
369369
},
370+
{
371+
name: "no rewards if ballot is not finalized,irrespective of votes",
372+
votes: [][]observertypes.VoteType{
373+
{
374+
observertypes.VoteType_NotYetVoted,
375+
observertypes.VoteType_NotYetVoted,
376+
observertypes.VoteType_SuccessObservation,
377+
observertypes.VoteType_FailureObservation,
378+
},
379+
},
380+
observerStartingEmissions: sdkmath.NewInt(100),
381+
// total reward units would be 4 as all votes match the ballot status
382+
totalRewardsForBlock: sdkmath.NewInt(0),
383+
expectedRewards: map[string]int64{
384+
observerSet.ObserverList[0]: 100,
385+
observerSet.ObserverList[1]: 100,
386+
observerSet.ObserverList[2]: 100,
387+
observerSet.ObserverList[3]: 100,
388+
},
389+
ballotStatus: observertypes.BallotStatus_BallotInProgress,
390+
slashAmount: sdkmath.NewInt(25),
391+
rewardsPerBlock: emissionstypes.BlockReward,
392+
},
370393
{
371394
name: "one observer slashed",
372395
votes: [][]observertypes.VoteType{
@@ -537,6 +560,7 @@ func TestDistributeObserverRewards(t *testing.T) {
537560
}
538561
for _, tc := range tt {
539562
t.Run(tc.name, func(t *testing.T) {
563+
// Arrange
540564
// Keeper initialization
541565
k, ctx, sk, zk := keepertest.EmissionsKeeper(t)
542566
zk.ObserverKeeper.SetObserverSet(ctx, observerSet)
@@ -581,10 +605,12 @@ func TestDistributeObserverRewards(t *testing.T) {
581605
})
582606
ctx = ctx.WithBlockHeight(100)
583607

608+
// Act
584609
// Distribute the rewards and check if the rewards are distributed correctly
585610
err = emissions.DistributeObserverRewards(ctx, tc.totalRewardsForBlock, *k, params)
586-
require.NoError(t, err)
587611

612+
// Assert
613+
require.NoError(t, err)
588614
for i, observer := range observerSet.ObserverList {
589615
observerEmission, found := k.GetWithdrawableEmission(ctx, observer)
590616
require.True(t, found, "withdrawable emission not found for observer %d", i)
@@ -596,6 +622,12 @@ func TestDistributeObserverRewards(t *testing.T) {
596622
i,
597623
)
598624
}
625+
if tc.ballotStatus != observertypes.BallotStatus_BallotInProgress {
626+
require.Len(t, zk.ObserverKeeper.GetAllBallots(ctx), 0)
627+
_, found := zk.ObserverKeeper.GetBallotListForHeight(ctx, 0)
628+
require.False(t, found)
629+
}
630+
599631
})
600632
}
601633
}

x/emissions/keeper/migrator.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import (
44
sdk "github.com/cosmos/cosmos-sdk/types"
55

66
"github.com/zeta-chain/node/x/emissions/exported"
7-
v3 "github.com/zeta-chain/node/x/emissions/migrations/v3"
87
v4 "github.com/zeta-chain/node/x/emissions/migrations/v4"
8+
v5 "github.com/zeta-chain/node/x/emissions/migrations/v5"
99
)
1010

1111
// Migrator is a struct for handling in-place store migrations.
@@ -22,12 +22,12 @@ func NewMigrator(k Keeper, ss exported.Subspace) Migrator {
2222
}
2323
}
2424

25-
// Migrate2to3 migrates the store from consensus version 2 to 3
26-
func (m Migrator) Migrate2to3(ctx sdk.Context) error {
27-
return v3.MigrateStore(ctx, m.keeper, m.legacySubspace)
28-
}
29-
3025
// Migrate3to4 migrates the store from consensus version 3 to 4
3126
func (m Migrator) Migrate3to4(ctx sdk.Context) error {
3227
return v4.MigrateStore(ctx, m.keeper)
3328
}
29+
30+
// Migrate4to5 migrates the store from consensus version 4 to 5
31+
func (m Migrator) Migrate4to5(ctx sdk.Context) error {
32+
return v5.MigrateStore(ctx, m.keeper)
33+
}

x/emissions/keeper/msg_server_update_params_test.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func TestMsgServer_UpdateParams(t *testing.T) {
4040
require.Error(t, err)
4141
})
4242

43-
t.Run("fail for invalid params", func(t *testing.T) {
43+
t.Run("fail for invalid params ,validatorEmissionPercentage is invalid", func(t *testing.T) {
4444
k, ctx, _, _ := keepertest.EmissionsKeeper(t)
4545
msgServer := keeper.NewMsgServerImpl(*k)
4646
params := types.DefaultParams()
@@ -52,4 +52,17 @@ func TestMsgServer_UpdateParams(t *testing.T) {
5252

5353
require.ErrorIs(t, err, types.ErrUnableToSetParams)
5454
})
55+
56+
t.Run("fail for invalid params ,pending buffer blocks is invalid", func(t *testing.T) {
57+
k, ctx, _, _ := keepertest.EmissionsKeeper(t)
58+
msgServer := keeper.NewMsgServerImpl(*k)
59+
params := types.DefaultParams()
60+
params.PendingBallotsDeletionBufferBlocks = -1
61+
_, err := msgServer.UpdateParams(ctx, &types.MsgUpdateParams{
62+
Authority: k.GetAuthority(),
63+
Params: params,
64+
})
65+
66+
require.ErrorIs(t, err, types.ErrUnableToSetParams)
67+
})
5568
}

0 commit comments

Comments
 (0)