mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-11-10 10:05:18 +00:00
add proposal voting deadlines
This commit is contained in:
parent
20bcfec407
commit
f773f7f278
@ -282,7 +282,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
|
|||||||
// there is nothing left over in the validator fee pool, so as to keep the
|
// there is nothing left over in the validator fee pool, so as to keep the
|
||||||
// CanWithdrawInvariant invariant.
|
// CanWithdrawInvariant invariant.
|
||||||
// Auction.BeginBlocker will close out expired auctions and pay debt back to cdp. So it should be run before cdp.BeginBlocker which cancels out debt with stable and starts more auctions.
|
// Auction.BeginBlocker will close out expired auctions and pay debt back to cdp. So it should be run before cdp.BeginBlocker which cancels out debt with stable and starts more auctions.
|
||||||
app.mm.SetOrderBeginBlockers(mint.ModuleName, distr.ModuleName, slashing.ModuleName, validatorvesting.ModuleName, auction.ModuleName, cdp.ModuleName)
|
app.mm.SetOrderBeginBlockers(mint.ModuleName, distr.ModuleName, slashing.ModuleName, validatorvesting.ModuleName, auction.ModuleName, cdp.ModuleName, committee.ModuleName)
|
||||||
|
|
||||||
app.mm.SetOrderEndBlockers(crisis.ModuleName, gov.ModuleName, staking.ModuleName, pricefeed.ModuleName)
|
app.mm.SetOrderEndBlockers(crisis.ModuleName, gov.ModuleName, staking.ModuleName, pricefeed.ModuleName)
|
||||||
|
|
||||||
|
@ -1,12 +1,23 @@
|
|||||||
package committee
|
package committee
|
||||||
|
|
||||||
// func BeginBlocker() {
|
import (
|
||||||
// // TODO much the same as the current gov endblocker does
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
|
|
||||||
// // Get all active proposals
|
"github.com/kava-labs/kava/x/committee/types"
|
||||||
// // If voting periods are over, tally up the results
|
)
|
||||||
// // If a proposal passes run it through the correct handler
|
|
||||||
// // Handler need to be registered in app.go as they are for the current gov module
|
// BeginBlocker runs at the start of every block.
|
||||||
// handler := keeper.Router().GetRoute(proposal.ProposalRoute())
|
func BeginBlocker(ctx sdk.Context, _ abci.RequestBeginBlock, k Keeper) {
|
||||||
// err := handler(ctx, proposal.Content)
|
|
||||||
// }
|
// Close all expired proposals
|
||||||
|
// TODO optimize by using an index to avoid iterating over non expired proposals
|
||||||
|
k.IterateProposals(ctx, func(proposal types.Proposal) bool {
|
||||||
|
if proposal.HasExpiredBy(ctx.BlockTime()) {
|
||||||
|
if err := k.CloseOutProposal(ctx, proposal.ID); err != nil {
|
||||||
|
panic(err) // if an expired proposal does not close then something has gone very wrong
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
68
x/committee/abci_test.go
Normal file
68
x/committee/abci_test.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
package committee_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/gov"
|
||||||
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
|
|
||||||
|
"github.com/kava-labs/kava/app"
|
||||||
|
"github.com/kava-labs/kava/x/committee"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ModuleTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
keeper committee.Keeper
|
||||||
|
app app.TestApp
|
||||||
|
ctx sdk.Context
|
||||||
|
|
||||||
|
addresses []sdk.AccAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ModuleTestSuite) SetupTest() {
|
||||||
|
suite.app = app.NewTestApp()
|
||||||
|
suite.keeper = suite.app.GetCommitteeKeeper()
|
||||||
|
suite.ctx = suite.app.NewContext(true, abci.Header{})
|
||||||
|
_, suite.addresses = app.GeneratePrivKeyAddressPairs(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ModuleTestSuite) TestBeginBlock() {
|
||||||
|
suite.app.InitializeFromGenesisStates()
|
||||||
|
// TODO replace below with genesis state
|
||||||
|
normalCom := committee.Committee{
|
||||||
|
ID: 12,
|
||||||
|
Members: suite.addresses[:2],
|
||||||
|
Permissions: []committee.Permission{committee.GodPermission{}},
|
||||||
|
}
|
||||||
|
suite.keeper.SetCommittee(suite.ctx, normalCom)
|
||||||
|
|
||||||
|
pprop1 := gov.NewTextProposal("1A Title", "A description of this proposal.")
|
||||||
|
id1, err := suite.keeper.SubmitProposal(suite.ctx, normalCom.Members[0], normalCom.ID, pprop1)
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
oneHrLaterCtx := suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Hour))
|
||||||
|
pprop2 := gov.NewTextProposal("2A Title", "A description of this proposal.")
|
||||||
|
id2, err := suite.keeper.SubmitProposal(oneHrLaterCtx, normalCom.Members[0], normalCom.ID, pprop2)
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
// Run BeginBlocker
|
||||||
|
proposalDurationLaterCtx := suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(committee.MaxProposalDuration))
|
||||||
|
suite.NotPanics(func() {
|
||||||
|
committee.BeginBlocker(proposalDurationLaterCtx, abci.RequestBeginBlock{}, suite.keeper)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check expired proposals are gone
|
||||||
|
_, found := suite.keeper.GetProposal(suite.ctx, id1)
|
||||||
|
suite.False(found, "expected expired proposal to be closed")
|
||||||
|
_, found = suite.keeper.GetProposal(suite.ctx, id2)
|
||||||
|
suite.True(found, "expected non expired proposal to be not closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestModuleTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(ModuleTestSuite))
|
||||||
|
}
|
@ -9,27 +9,35 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
DefaultNextProposalID = types.DefaultNextProposalID
|
DefaultNextProposalID = types.DefaultNextProposalID
|
||||||
|
DefaultParamspace = types.DefaultParamspace
|
||||||
ModuleName = types.ModuleName
|
ModuleName = types.ModuleName
|
||||||
|
QuerierRoute = types.QuerierRoute
|
||||||
|
RouterKey = types.RouterKey
|
||||||
StoreKey = types.StoreKey
|
StoreKey = types.StoreKey
|
||||||
|
TypeMsgSubmitProposal = types.TypeMsgSubmitProposal
|
||||||
|
TypeMsgVote = types.TypeMsgVote
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// function aliases
|
// function aliases
|
||||||
NewKeeper = keeper.NewKeeper
|
NewKeeper = keeper.NewKeeper
|
||||||
DefaultGenesisState = types.DefaultGenesisState
|
DefaultGenesisState = types.DefaultGenesisState
|
||||||
GetKeyFromID = types.GetKeyFromID
|
GetKeyFromID = types.GetKeyFromID
|
||||||
GetVoteKey = types.GetVoteKey
|
GetVoteKey = types.GetVoteKey
|
||||||
NewGenesisState = types.NewGenesisState
|
NewGenesisState = types.NewGenesisState
|
||||||
RegisterCodec = types.RegisterCodec
|
NewMsgSubmitProposal = types.NewMsgSubmitProposal
|
||||||
Uint64FromBytes = types.Uint64FromBytes
|
NewMsgVote = types.NewMsgVote
|
||||||
|
RegisterCodec = types.RegisterCodec
|
||||||
|
Uint64FromBytes = types.Uint64FromBytes
|
||||||
|
|
||||||
// variable aliases
|
// variable aliases
|
||||||
CommitteeKeyPrefix = types.CommitteeKeyPrefix
|
CommitteeKeyPrefix = types.CommitteeKeyPrefix
|
||||||
ModuleCdc = types.ModuleCdc
|
MaxProposalDuration = types.MaxProposalDuration
|
||||||
NextProposalIDKey = types.NextProposalIDKey
|
ModuleCdc = types.ModuleCdc
|
||||||
ProposalKeyPrefix = types.ProposalKeyPrefix
|
NextProposalIDKey = types.NextProposalIDKey
|
||||||
VoteKeyPrefix = types.VoteKeyPrefix
|
ProposalKeyPrefix = types.ProposalKeyPrefix
|
||||||
VoteThreshold = types.VoteThreshold
|
VoteKeyPrefix = types.VoteKeyPrefix
|
||||||
|
VoteThreshold = types.VoteThreshold
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
package keeper
|
package keeper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
"github.com/cosmos/cosmos-sdk/store/prefix"
|
"github.com/cosmos/cosmos-sdk/store/prefix"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
|
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
|
||||||
|
|
||||||
"github.com/kava-labs/kava/x/committee/types"
|
"github.com/kava-labs/kava/x/committee/types"
|
||||||
@ -94,7 +95,7 @@ func (k Keeper) IncrementNextProposalID(ctx sdk.Context) sdk.Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// StoreNewProposal stores a proposal, adding a new ID
|
// StoreNewProposal stores a proposal, adding a new ID
|
||||||
func (k Keeper) StoreNewProposal(ctx sdk.Context, committeeID uint64, pubProposal types.PubProposal) (uint64, sdk.Error) {
|
func (k Keeper) StoreNewProposal(ctx sdk.Context, pubProposal types.PubProposal, committeeID uint64, deadline time.Time) (uint64, sdk.Error) {
|
||||||
newProposalID, err := k.GetNextProposalID(ctx)
|
newProposalID, err := k.GetNextProposalID(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
@ -103,6 +104,7 @@ func (k Keeper) StoreNewProposal(ctx sdk.Context, committeeID uint64, pubProposa
|
|||||||
PubProposal: pubProposal,
|
PubProposal: pubProposal,
|
||||||
ID: newProposalID,
|
ID: newProposalID,
|
||||||
CommitteeID: committeeID,
|
CommitteeID: committeeID,
|
||||||
|
Deadline: deadline,
|
||||||
}
|
}
|
||||||
|
|
||||||
k.SetProposal(ctx, proposal)
|
k.SetProposal(ctx, proposal)
|
||||||
@ -139,6 +141,22 @@ func (k Keeper) DeleteProposal(ctx sdk.Context, proposalID uint64) {
|
|||||||
store.Delete(types.GetKeyFromID(proposalID))
|
store.Delete(types.GetKeyFromID(proposalID))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IterateProposals provides an iterator over all stored proposals.
|
||||||
|
// For each proposal, cb will be called. If cb returns true, the iterator will close and stop.
|
||||||
|
func (k Keeper) IterateProposals(ctx sdk.Context, cb func(proposal types.Proposal) (stop bool)) {
|
||||||
|
iterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.storeKey), types.ProposalKeyPrefix)
|
||||||
|
|
||||||
|
defer iterator.Close()
|
||||||
|
for ; iterator.Valid(); iterator.Next() {
|
||||||
|
var proposal types.Proposal
|
||||||
|
k.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &proposal)
|
||||||
|
|
||||||
|
if cb(proposal) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- Votes ----------
|
// ---------- Votes ----------
|
||||||
|
|
||||||
// GetVote gets a vote from the store.
|
// GetVote gets a vote from the store.
|
||||||
|
@ -27,7 +27,8 @@ func (k Keeper) SubmitProposal(ctx sdk.Context, proposer sdk.AccAddress, committ
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get a new ID and store the proposal
|
// Get a new ID and store the proposal
|
||||||
return k.StoreNewProposal(ctx, committeeID, pubProposal)
|
deadline := ctx.BlockTime().Add(types.MaxProposalDuration)
|
||||||
|
return k.StoreNewProposal(ctx, pubProposal, committeeID, deadline)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k Keeper) AddVote(ctx sdk.Context, proposalID uint64, voter sdk.AccAddress) sdk.Error {
|
func (k Keeper) AddVote(ctx sdk.Context, proposalID uint64, voter sdk.AccAddress) sdk.Error {
|
||||||
@ -36,6 +37,9 @@ func (k Keeper) AddVote(ctx sdk.Context, proposalID uint64, voter sdk.AccAddress
|
|||||||
if !found {
|
if !found {
|
||||||
return sdk.ErrInternal("proposal not found")
|
return sdk.ErrInternal("proposal not found")
|
||||||
}
|
}
|
||||||
|
if pr.HasExpiredBy(ctx.BlockTime()) {
|
||||||
|
return sdk.ErrInternal("proposal expired")
|
||||||
|
}
|
||||||
com, found := k.GetCommittee(ctx, pr.CommitteeID)
|
com, found := k.GetCommittee(ctx, pr.CommitteeID)
|
||||||
if !found {
|
if !found {
|
||||||
return sdk.ErrInternal("committee disbanded")
|
return sdk.ErrInternal("committee disbanded")
|
||||||
@ -65,7 +69,9 @@ func (k Keeper) CloseOutProposal(ctx sdk.Context, proposalID uint64) sdk.Error {
|
|||||||
votes = append(votes, vote)
|
votes = append(votes, vote)
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
if sdk.NewDec(int64(len(votes))).GTE(types.VoteThreshold.MulInt64(int64(len(com.Members)))) { // TODO move vote counting stuff to committee methods // TODO add timeout check here - close if expired regardless of votes
|
proposalPasses := sdk.NewDec(int64(len(votes))).GTE(types.VoteThreshold.MulInt64(int64(len(com.Members))))
|
||||||
|
|
||||||
|
if proposalPasses {
|
||||||
// eneact vote
|
// eneact vote
|
||||||
// The proposal handler may execute state mutating logic depending
|
// The proposal handler may execute state mutating logic depending
|
||||||
// on the proposal content. If the handler fails, no state mutation
|
// on the proposal content. If the handler fails, no state mutation
|
||||||
@ -77,16 +83,17 @@ func (k Keeper) CloseOutProposal(ctx sdk.Context, proposalID uint64) sdk.Error {
|
|||||||
// write state to the underlying multi-store
|
// write state to the underlying multi-store
|
||||||
writeCache()
|
writeCache()
|
||||||
} // if handler returns error, then still delete the proposal - it's still over, but send an event
|
} // if handler returns error, then still delete the proposal - it's still over, but send an event
|
||||||
|
}
|
||||||
|
if proposalPasses || pr.HasExpiredBy(ctx.BlockTime()) {
|
||||||
|
|
||||||
// delete proposal and votes
|
// delete proposal and votes
|
||||||
k.DeleteProposal(ctx, proposalID)
|
k.DeleteProposal(ctx, proposalID)
|
||||||
for _, v := range votes {
|
for _, v := range votes {
|
||||||
k.DeleteVote(ctx, v.ProposalID, v.Voter)
|
k.DeleteVote(ctx, v.ProposalID, v.Voter)
|
||||||
}
|
}
|
||||||
} else {
|
return nil
|
||||||
return sdk.ErrInternal("note enough votes to close proposal")
|
|
||||||
}
|
}
|
||||||
return nil
|
return sdk.ErrInternal("note enough votes to close proposal")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k Keeper) ValidatePubProposal(ctx sdk.Context, pubProposal types.PubProposal) sdk.Error {
|
func (k Keeper) ValidatePubProposal(ctx sdk.Context, pubProposal types.PubProposal) sdk.Error {
|
||||||
|
@ -2,6 +2,7 @@ package keeper_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/cosmos/cosmos-sdk/x/gov"
|
"github.com/cosmos/cosmos-sdk/x/gov"
|
||||||
@ -89,8 +90,10 @@ func (suite *KeeperTestSuite) TestSubmitProposal() {
|
|||||||
|
|
||||||
if tc.expectPass {
|
if tc.expectPass {
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
_, found := keeper.GetProposal(ctx, id)
|
pr, found := keeper.GetProposal(ctx, id)
|
||||||
suite.True(found)
|
suite.True(found)
|
||||||
|
suite.Equal(tc.committeeID, pr.CommitteeID)
|
||||||
|
suite.Equal(ctx.BlockTime().Add(types.MaxProposalDuration), pr.Deadline)
|
||||||
} else {
|
} else {
|
||||||
suite.NotNil(err)
|
suite.NotNil(err)
|
||||||
}
|
}
|
||||||
@ -104,11 +107,13 @@ func (suite *KeeperTestSuite) TestAddVote() {
|
|||||||
Members: suite.addresses[:2],
|
Members: suite.addresses[:2],
|
||||||
Permissions: []types.Permission{types.GodPermission{}},
|
Permissions: []types.Permission{types.GodPermission{}},
|
||||||
}
|
}
|
||||||
|
firstBlockTime := time.Date(1998, time.January, 1, 1, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
testcases := []struct {
|
testcases := []struct {
|
||||||
name string
|
name string
|
||||||
proposalID uint64
|
proposalID uint64
|
||||||
voter sdk.AccAddress
|
voter sdk.AccAddress
|
||||||
|
voteTime time.Time
|
||||||
expectPass bool
|
expectPass bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@ -129,6 +134,13 @@ func (suite *KeeperTestSuite) TestAddVote() {
|
|||||||
voter: suite.addresses[4],
|
voter: suite.addresses[4],
|
||||||
expectPass: false,
|
expectPass: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "proposal expired",
|
||||||
|
proposalID: types.DefaultNextProposalID,
|
||||||
|
voter: normalCom.Members[0],
|
||||||
|
voteTime: firstBlockTime.Add(types.MaxProposalDuration),
|
||||||
|
expectPass: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testcases {
|
for _, tc := range testcases {
|
||||||
@ -136,7 +148,7 @@ func (suite *KeeperTestSuite) TestAddVote() {
|
|||||||
// Create local testApp because suite doesn't run the SetupTest function for subtests, which would mean the app state is not be reset between subtests.
|
// Create local testApp because suite doesn't run the SetupTest function for subtests, which would mean the app state is not be reset between subtests.
|
||||||
tApp := app.NewTestApp()
|
tApp := app.NewTestApp()
|
||||||
keeper := tApp.GetCommitteeKeeper()
|
keeper := tApp.GetCommitteeKeeper()
|
||||||
ctx := tApp.NewContext(true, abci.Header{})
|
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: firstBlockTime})
|
||||||
tApp.InitializeFromGenesisStates()
|
tApp.InitializeFromGenesisStates()
|
||||||
|
|
||||||
// setup the committee and proposal
|
// setup the committee and proposal
|
||||||
@ -144,6 +156,7 @@ func (suite *KeeperTestSuite) TestAddVote() {
|
|||||||
_, err := keeper.SubmitProposal(ctx, normalCom.Members[0], normalCom.ID, gov.NewTextProposal("A Title", "A description of this proposal."))
|
_, err := keeper.SubmitProposal(ctx, normalCom.Members[0], normalCom.ID, gov.NewTextProposal("A Title", "A description of this proposal."))
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
|
|
||||||
|
ctx = ctx.WithBlockTime(tc.voteTime)
|
||||||
err = keeper.AddVote(ctx, tc.proposalID, tc.voter)
|
err = keeper.AddVote(ctx, tc.proposalID, tc.voter)
|
||||||
|
|
||||||
if tc.expectPass {
|
if tc.expectPass {
|
||||||
|
@ -150,7 +150,7 @@ func (am AppModule) ExportGenesis(ctx sdk.Context) json.RawMessage {
|
|||||||
|
|
||||||
// BeginBlock module begin-block
|
// BeginBlock module begin-block
|
||||||
func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) {
|
func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) {
|
||||||
// TODO BeginBlocker(ctx, req, am.keeper)
|
BeginBlocker(ctx, req, am.keeper)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EndBlock module end-block
|
// EndBlock module end-block
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/cosmos/cosmos-sdk/x/gov"
|
"github.com/cosmos/cosmos-sdk/x/gov"
|
||||||
)
|
)
|
||||||
|
|
||||||
// -------- Committees --------
|
// TODO move these into params
|
||||||
|
var (
|
||||||
|
VoteThreshold sdk.Dec = sdk.MustNewDecFromStr("0.75")
|
||||||
|
MaxProposalDuration time.Duration = time.Hour * 24 * 7
|
||||||
|
)
|
||||||
|
|
||||||
var VoteThreshold sdk.Dec = sdk.MustNewDecFromStr("0.75")
|
// -------- Committees --------
|
||||||
|
|
||||||
// A Committee is a collection of addresses that are allowed to vote and enact any governance proposal that passes their permissions.
|
// A Committee is a collection of addresses that are allowed to vote and enact any governance proposal that passes their permissions.
|
||||||
type Committee struct {
|
type Committee struct {
|
||||||
@ -50,6 +56,13 @@ type Proposal struct {
|
|||||||
PubProposal
|
PubProposal
|
||||||
ID uint64
|
ID uint64
|
||||||
CommitteeID uint64
|
CommitteeID uint64
|
||||||
|
Deadline time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasExpiredBy calculates if the proposal will have expired by a certain time.
|
||||||
|
// All votes must be cast before deadline, those cast at time == deadline are not valid
|
||||||
|
func (p Proposal) HasExpiredBy(time time.Time) bool {
|
||||||
|
return !time.Before(p.Deadline)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Vote struct {
|
type Vote struct {
|
||||||
|
Loading…
Reference in New Issue
Block a user