Skip to content

Commit

Permalink
Feat/gov test (#46)
Browse files Browse the repository at this point in the history
* make emergency proposals run at each tally interval

* add gov test

* apply requested changes

* get changed proposal in the if statement
  • Loading branch information
sh-cha authored Jan 11, 2024
1 parent d85b823 commit 7632328
Show file tree
Hide file tree
Showing 29 changed files with 4,123 additions and 700 deletions.
4 changes: 2 additions & 2 deletions app/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func SetupWithGenesisAccounts(
bondAmt := sdk.TokensFromConsensusPower(1, sdk.DefaultPowerReduction)
bondCoins := sdk.NewCoins(sdk.NewCoin(BondDenom, bondAmt))

for _, val := range valSet.Validators {
for i, val := range valSet.Validators {
pk, err := cryptocodec.FromCmtPubKeyInterface(val.PubKey)
if err != nil {
panic(err)
Expand All @@ -133,7 +133,7 @@ func SetupWithGenesisAccounts(
}

validators = append(validators, validator)
delegations = append(delegations, stakingtypes.NewDelegation(genAccs[0].GetAddress().String(), sdk.ValAddress(val.Address).String(), sdk.NewDecCoins(sdk.NewDecCoinFromDec(BondDenom, math.LegacyOneDec()))))
delegations = append(delegations, stakingtypes.NewDelegation(genAccs[i].GetAddress().String(), sdk.ValAddress(val.Address).String(), sdk.NewDecCoins(sdk.NewDecCoinFromDec(BondDenom, math.LegacyOneDec()))))
}

// set validators and delegations
Expand Down
9 changes: 2 additions & 7 deletions proto/initia/gov/v1/genesis.proto
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,14 @@ message GenesisState {
// votes defines all the votes present at genesis.
repeated cosmos.gov.v1.Vote votes = 3;
// proposals defines all the proposals present at genesis.
repeated cosmos.gov.v1.Proposal proposals = 4;
repeated Proposal proposals = 4;
// params defines all the paramaters of x/gov module.
initia.gov.v1.Params params = 5;
Params params = 5;
// The constitution allows builders to lay a foundation and define purpose.
// This is an immutable string set in genesis.
// There are no amendments, to go outside of scope, just fork.
// constitution is an immutable string in genesis for a chain builder to lay out their vision, ideas and ideals.
//
// Since: cosmos-sdk 0.50
string constitution = 6;

// last_emergency_proposal_tally_timestamp defines the last time of
// tally for emergency proposals.
google.protobuf.Timestamp last_emergency_proposal_tally_timestamp = 91
[(gogoproto.stdtime) = true, (gogoproto.nullable) = false, (amino.dont_omitempty) = true];
}
69 changes: 68 additions & 1 deletion proto/initia/gov/v1/gov.proto
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import "cosmos/gov/v1/gov.proto";
import "amino/amino.proto";

option go_package = "github.com/initia-labs/initia/x/gov/types";
option (gogoproto.equal_all) = true;
// option (gogoproto.equal_all) = true;

// Params defines the parameters for the x/gov module.
message Params {
Expand Down Expand Up @@ -95,3 +95,70 @@ message Params {
google.protobuf.Duration emergency_tally_interval = 92
[(gogoproto.stdduration) = true, (gogoproto.nullable) = false, (amino.dont_omitempty) = true];
}

// Proposal defines the core field members of a governance proposal.
message Proposal {
// id defines the unique id of the proposal.
uint64 id = 1;

// messages are the arbitrary messages to be executed if the proposal passes.
repeated google.protobuf.Any messages = 2;

// status defines the proposal status.
cosmos.gov.v1.ProposalStatus status = 3;

// final_tally_result is the final tally result of the proposal. When
// querying a proposal via gRPC, this field is not populated until the
// proposal's voting period has ended.
cosmos.gov.v1.TallyResult final_tally_result = 4;

// submit_time is the time of proposal submission.
google.protobuf.Timestamp submit_time = 5 [(gogoproto.stdtime) = true];

// deposit_end_time is the end time for deposition.
google.protobuf.Timestamp deposit_end_time = 6 [(gogoproto.stdtime) = true];

// total_deposit is the total deposit on the proposal.
repeated cosmos.base.v1beta1.Coin total_deposit = 7 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true];

// voting_start_time is the starting time to vote on a proposal.
google.protobuf.Timestamp voting_start_time = 8 [(gogoproto.stdtime) = true];

// voting_end_time is the end time of voting on a proposal.
google.protobuf.Timestamp voting_end_time = 9 [(gogoproto.stdtime) = true];

google.protobuf.Timestamp emergency_start_time = 10 [(gogoproto.stdtime) = true];
google.protobuf.Timestamp emergency_next_tally_time = 11 [(gogoproto.stdtime) = true];

// metadata is any arbitrary metadata attached to the proposal.
// the recommended format of the metadata is to be found here:
// https://docs.cosmos.network/v0.47/modules/gov#proposal-3
string metadata = 12;

// title is the title of the proposal
//
// Since: cosmos-sdk 0.47
string title = 13;

// summary is a short summary of the proposal
//
// Since: cosmos-sdk 0.47
string summary = 14;

// proposer is the address of the proposal sumbitter
//
// Since: cosmos-sdk 0.47
string proposer = 15 [(cosmos_proto.scalar) = "cosmos.AddressString"];

// expedited defines if the proposal is expedited
//
// Since: cosmos-sdk 0.50
bool expedited = 16;

bool emergency = 17;

// failed_reason defines the reason why the proposal failed
//
// Since: cosmos-sdk 0.50
string failed_reason = 18;
}
59 changes: 45 additions & 14 deletions proto/initia/gov/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,16 @@ service Query {
option (google.api.http).get = "/initia/gov/v1/emergency_proposals";
}

// LastEmergencyProposalTallyTimestamp queries last emergency proposal tally time.
rpc LastEmergencyProposalTallyTimestamp(QueryLastEmergencyProposalTallyTimestampRequest)
returns (QueryLastEmergencyProposalTallyTimestampResponse) {
option (google.api.http).get = "/initia/gov/v1/last_emergency_proposal_tally_timestamp";
}
// Proposal queries proposal details based on ProposalID.
rpc Proposal(QueryProposalRequest) returns (QueryProposalResponse) {
option (google.api.http).get = "/initia/gov/v1/proposals/{proposal_id}";
}

// Proposals queries all proposals based on given status.
rpc Proposals(QueryProposalsRequest) returns (QueryProposalsResponse) {
option (google.api.http).get = "/initia/gov/v1/proposals";
}

}

// QueryParamsRequest is the request type for the Query/Params RPC method.
Expand All @@ -53,19 +58,45 @@ message QueryEmergencyProposalsRequest {
// QueryEmergencyProposalsResponse is the response type for the
// Query/EmergencyProposals RPC method.
message QueryEmergencyProposalsResponse {
repeated cosmos.gov.v1.Proposal proposals = 1 [(gogoproto.nullable) = false];
repeated Proposal proposals = 1 [(gogoproto.nullable) = false];

// pagination defines the pagination in the response.
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

// QueryLastEmergencyProposalTallyTimestampRequest is the request
// type for the Query/LastEmergencyProposalTallyTimestamp RPC method.
message QueryLastEmergencyProposalTallyTimestampRequest {}
// QueryProposalRequest is the request type for the Query/Proposal RPC method.
message QueryProposalRequest {
// proposal_id defines the unique id of the proposal.
uint64 proposal_id = 1;
}

// QueryLastEmergencyProposalTallyTimestampResponse is the response type for the
// Query/LastEmergencyProposalTallyTimestamp RPC method.
message QueryLastEmergencyProposalTallyTimestampResponse {
google.protobuf.Timestamp tally_timestamp = 1
[(gogoproto.stdtime) = true, (gogoproto.nullable) = false, (amino.dont_omitempty) = true];
// QueryProposalResponse is the response type for the Query/Proposal RPC method.
message QueryProposalResponse {
// proposal is the requested governance proposal.
Proposal proposal = 1;
}

// QueryProposalsRequest is the request type for the Query/Proposals RPC method.
message QueryProposalsRequest {
// proposal_status defines the status of the proposals.
cosmos.gov.v1.ProposalStatus proposal_status = 1;

// voter defines the voter address for the proposals.
string voter = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"];

// depositor defines the deposit addresses from the proposals.
string depositor = 3 [(cosmos_proto.scalar) = "cosmos.AddressString"];

// pagination defines an optional pagination for the request.
cosmos.base.query.v1beta1.PageRequest pagination = 4;
}

// QueryProposalsResponse is the response type for the Query/Proposals RPC
// method.
message QueryProposalsResponse {
// proposals defines all the requested governance proposals.
repeated Proposal proposals = 1;

// pagination defines the pagination in the response.
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}
103 changes: 64 additions & 39 deletions x/gov/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (
v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"

"github.com/initia-labs/initia/x/gov/keeper"

customtypes "github.com/initia-labs/initia/x/gov/types"
)

// EndBlocker called every block, process inflation, update validator set.
Expand Down Expand Up @@ -90,7 +92,7 @@ func EndBlocker(ctx sdk.Context, k *keeper.Keeper) error {
"proposal", proposal.Id,
"expedited", proposal.Expedited,
"title", proposal.Title,
"min_deposit", sdk.NewCoins(proposal.GetMinDepositFromParams(params.ToV1())...).String(),
"min_deposit", sdk.NewCoins(proposal.GetMinDepositFromParams(params)...).String(),
"total_deposit", sdk.NewCoins(proposal.TotalDeposit...).String(),
)

Expand All @@ -117,8 +119,13 @@ func EndBlocker(ctx sdk.Context, k *keeper.Keeper) error {
return false, err
}

if err = k.EmergencyProposals.Remove(ctx, proposal.Id); err != nil {
return false, err
if proposal.Emergency {
if err = k.EmergencyProposalsQueue.Remove(ctx, collections.Join(*proposal.EmergencyNextTallyTime, proposal.Id)); err != nil {
return false, err
}
if err = k.EmergencyProposals.Remove(ctx, proposal.Id); err != nil {
return false, err
}
}

return false, nil
Expand All @@ -143,58 +150,70 @@ func EndBlocker(ctx sdk.Context, k *keeper.Keeper) error {
return err
}

// periodically tally emergency proposal
lastEmergencyProposalTallyTimestamp, err := k.LastEmergencyProposalTallyTimestamp.Get(ctx)
if err != nil {
return err
}
err = k.EmergencyProposalsQueue.Walk(ctx, rng, func(key collections.Pair[time.Time, uint64], _ uint64) (bool, error) {
proposal, err := k.Proposals.Get(ctx, key.K2())
if err != nil {
// if the proposal has an encoding error, this means it cannot be processed by x/gov
// this could be due to some types missing their registration
// instead of returning an error (i.e, halting the chain), we fail the proposal
if errors.Is(err, collections.ErrEncoding) {
proposal.Id = key.K2()
if err := failUnsupportedProposal(ctx, k, proposal, err.Error(), true); err != nil {
return false, err
}

if ctx.BlockTime().After(lastEmergencyProposalTallyTimestamp.Add(params.EmergencyTallyInterval)) {
err = k.EmergencyProposals.Walk(ctx, nil, func(proposalID uint64, _ []byte) (stop bool, err error) {
proposal, err := k.Proposals.Get(ctx, proposalID)
if err != nil {
return false, err
}
if err = k.ActiveProposalsQueue.Remove(ctx, collections.Join(*proposal.VotingEndTime, proposal.Id)); err != nil {
return false, err
}

cacheCtx, writeCache := ctx.CacheContext()
if err = k.EmergencyProposalsQueue.Remove(ctx, key); err != nil {
return false, err
}

// Tally internally delete votes, so use cache context to prevent
// deleting votes of proposal in progress.
quorumReached, passed, burnDeposits, tallyResults, err := k.Tally(cacheCtx, proposal)
if err != nil {
return false, err
}
if !quorumReached {
if err = k.EmergencyProposals.Remove(ctx, proposal.Id); err != nil {
return false, err
}
return false, nil
}
return false, err
}
cacheCtx, writeCache := ctx.CacheContext()

// quorum reached; commit the state changes from k.Tally()
writeCache()
quorumReached, passed, burnDeposits, tallyResults, err := k.Tally(cacheCtx, proposal)
if err != nil {
return false, err
}

// handle tally result
err = handleTallyResult(ctx, k, proposal, passed, burnDeposits, tallyResults)
if err != nil {
if !quorumReached {
nextTallyTime := ctx.BlockTime().Add(params.EmergencyTallyInterval)
if err = k.EmergencyProposalsQueue.Set(ctx, collections.Join(nextTallyTime, proposal.Id), proposal.Id); err != nil {
return false, err
}
proposal.EmergencyNextTallyTime = &nextTallyTime
if err = k.EmergencyProposalsQueue.Remove(ctx, key); err != nil {
return false, err
}

return false, nil
})
if err != nil {
return err
}

if err := k.LastEmergencyProposalTallyTimestamp.Set(ctx, ctx.BlockTime()); err != nil {
return err
// quorum reached; commit the state changes from k.Tally()
writeCache()

err = handleTallyResult(ctx, k, proposal, passed, burnDeposits, tallyResults)
if err != nil {
return false, err
}
}

return nil
return false, nil
})

return err
}

func handleTallyResult(
ctx sdk.Context,
k *keeper.Keeper,
proposal v1.Proposal,
proposal customtypes.Proposal,
passed, burnDeposits bool,
tallyResults v1.TallyResult,
) (err error) {
Expand All @@ -217,8 +236,14 @@ func handleTallyResult(
return err
}

if err = k.EmergencyProposals.Remove(ctx, proposal.Id); err != nil {
return err
if proposal.Emergency {
if err = k.EmergencyProposalsQueue.Remove(ctx, collections.Join(*proposal.EmergencyNextTallyTime, proposal.Id)); err != nil {
return err
}

if err = k.EmergencyProposals.Remove(ctx, proposal.Id); err != nil {
return err
}
}

var tagValue, logMsg string
Expand Down Expand Up @@ -356,7 +381,7 @@ func safeExecuteHandler(ctx sdk.Context, msg sdk.Msg, handler baseapp.MsgService
func failUnsupportedProposal(
ctx sdk.Context,
k *keeper.Keeper,
proposal v1.Proposal,
proposal customtypes.Proposal,
errMsg string,
active bool,
) error {
Expand Down
Loading

0 comments on commit 7632328

Please sign in to comment.