Skip to content

Commit

Permalink
[PVM] DAO voting, AddProposalTx, AddVoteTx
Browse files Browse the repository at this point in the history
  • Loading branch information
evlekht committed Jul 19, 2023
1 parent 0c5035f commit a4998e4
Show file tree
Hide file tree
Showing 54 changed files with 7,646 additions and 81 deletions.
8 changes: 4 additions & 4 deletions config/camino.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@ import (
)

const (
DaoProposalBondAmountKey = "dao-proposal-bond-amount"
DACProposalBondAmountKey = "dac-proposal-bond-amount"
)

func addCaminoFlags(fs *flag.FlagSet) {
// Bond amount required to place a DAO proposal on the Primary Network
fs.Uint64(DaoProposalBondAmountKey, genesis.LocalParams.CaminoConfig.DaoProposalBondAmount, "Amount, in nAVAX, required to place a DAO proposal")
// Bond amount required to place a DAC proposal on the Primary Network
fs.Uint64(DACProposalBondAmountKey, genesis.LocalParams.CaminoConfig.DACProposalBondAmount, "Amount, in nAVAX, required to place a DAC proposal")
}

func getCaminoPlatformConfig(v *viper.Viper) caminoconfig.Config {
conf := caminoconfig.Config{
DaoProposalBondAmount: v.GetUint64(DaoProposalBondAmountKey),
DACProposalBondAmount: v.GetUint64(DACProposalBondAmountKey),
}
return conf
}
2 changes: 1 addition & 1 deletion genesis/genesis_camino.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ var (
SupplyCap: 1000 * units.MegaAvax,
},
CaminoConfig: caminoconfig.Config{
DaoProposalBondAmount: 1 * units.KiloAvax,
DACProposalBondAmount: 1 * units.KiloAvax,
},
},
}
Expand Down
2 changes: 1 addition & 1 deletion genesis/genesis_columbus.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ var (
SupplyCap: 1000 * units.MegaAvax,
},
CaminoConfig: caminoconfig.Config{
DaoProposalBondAmount: 100 * units.Avax,
DACProposalBondAmount: 100 * units.Avax,
},
},
}
Expand Down
2 changes: 1 addition & 1 deletion genesis/genesis_kopernikus.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ var (
SupplyCap: 1000 * units.MegaAvax,
},
CaminoConfig: caminoconfig.Config{
DaoProposalBondAmount: 100 * units.Avax,
DACProposalBondAmount: 100 * units.Avax,
},
},
}
Expand Down
2 changes: 1 addition & 1 deletion genesis/genesis_local.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ var (
SupplyCap: 720 * units.MegaAvax,
},
CaminoConfig: caminoconfig.Config{
DaoProposalBondAmount: 100 * units.Avax,
DACProposalBondAmount: 100 * units.Avax,
},
},
}
Expand Down
7 changes: 7 additions & 0 deletions version/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,13 @@ var (
}
AthensPhaseDefaultTime = time.Date(2023, time.July, 1, 8, 0, 0, 0, time.UTC)

DACPhaseTimes = map[uint32]time.Time{
constants.KopernikusID: time.Date(2023, time.July, 4, 13, 0, 0, 0, time.UTC),
constants.ColumbusID: time.Date(2023, time.July, 7, 8, 0, 0, 0, time.UTC),
constants.CaminoID: time.Date(2023, time.July, 17, 8, 0, 0, 0, time.UTC),
}
DACPhaseDefaultTime = time.Date(2023, time.July, 1, 8, 0, 0, 0, time.UTC)

// TODO: update this before release
CortinaTimes = map[uint32]time.Time{
constants.MainnetID: time.Date(10000, time.December, 1, 0, 0, 0, 0, time.UTC),
Expand Down
44 changes: 44 additions & 0 deletions vms/platformvm/blocks/builder/camino_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,28 @@ func caminoBuildBlock(
)
}

expiredProposalIDs, err := getExpiredProposals(parentState, timestamp)
if err != nil {
return nil, fmt.Errorf("could not find expired proposals: %w", err)
}
finishedProposalIDs, err := parentState.GetProposalIDsToFinish()
if err != nil {
return nil, fmt.Errorf("could not find successful proposals: %w", err)
}
if len(expiredProposalIDs) > 0 || len(finishedProposalIDs) > 0 {
finishProposalsTx, err := txBuilder.FinishProposalsTx(append(expiredProposalIDs, finishedProposalIDs...))
if err != nil {
return nil, fmt.Errorf("could not build tx to finish proposals: %w", err)
}

return blocks.NewBanffStandardBlock(
timestamp,
parentID,
height,
[]*txs.Tx{finishProposalsTx},
)
}

return nil, nil
}

Expand Down Expand Up @@ -133,3 +155,25 @@ func getNextDepositsToUnlock(

return nextDeposits, nextDepositsEndtime.Equal(chainTime), nil
}

func getExpiredProposals(
preferredState state.Chain,
chainTime time.Time,
) ([]ids.ID, error) {
if !chainTime.Before(mockable.MaxTime) {
return nil, errEndOfTime
}

nextProposals, nextProposalsEndtime, err := preferredState.GetNextToExpireProposalIDsAndTime(nil)
if err == database.ErrNotFound {
return nil, nil
} else if err != nil {
return nil, err
}

if nextProposalsEndtime.Equal(chainTime) {
return nextProposals, nil
}

return nil, nil
}
2 changes: 1 addition & 1 deletion vms/platformvm/blocks/executor/proposal_block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ func TestBanffProposalBlockTimeVerification(t *testing.T) {
onParentAccept.EXPECT().GetDeferredStakerIterator().Return(deferredStakersIt, nil).AnyTimes()

onParentAccept.EXPECT().GetNextToUnlockDepositTime(nil).Return(time.Time{}, database.ErrNotFound).AnyTimes()
onParentAccept.EXPECT().GetNextToUnlockDepositIDsAndTime(nil).Return(nil, time.Time{}, database.ErrNotFound).AnyTimes()
onParentAccept.EXPECT().GetNextProposalExpirationTime(nil).Return(time.Time{}, database.ErrNotFound).AnyTimes()

env.mockedState.EXPECT().GetUptime(gomock.Any(), gomock.Any()).Return(
time.Duration(1000), /*upDuration*/
Expand Down
2 changes: 1 addition & 1 deletion vms/platformvm/blocks/executor/standard_block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ func TestBanffStandardBlockTimeVerification(t *testing.T) {
onParentAccept.EXPECT().GetDeferredStakerIterator().Return(deferredStakersIt, nil).AnyTimes()

onParentAccept.EXPECT().GetNextToUnlockDepositTime(nil).Return(time.Time{}, database.ErrNotFound).AnyTimes()
onParentAccept.EXPECT().GetNextToUnlockDepositIDsAndTime(nil).Return(nil, time.Time{}, database.ErrNotFound).AnyTimes()
onParentAccept.EXPECT().GetNextProposalExpirationTime(nil).Return(time.Time{}, database.ErrNotFound).AnyTimes()

onParentAccept.EXPECT().GetTimestamp().Return(chainTime).AnyTimes()

Expand Down
2 changes: 1 addition & 1 deletion vms/platformvm/camino_helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ func defaultCaminoConfig(postBanff bool) config.Config {
ApricotPhase5Time: defaultValidateEndTime,
BanffTime: banffTime,
CaminoConfig: caminoconfig.Config{
DaoProposalBondAmount: 100 * units.Avax,
DACProposalBondAmount: 100 * units.Avax,
},
}
}
Expand Down
2 changes: 1 addition & 1 deletion vms/platformvm/caminoconfig/camino_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
package caminoconfig

type Config struct {
DaoProposalBondAmount uint64
DACProposalBondAmount uint64
}
7 changes: 7 additions & 0 deletions vms/platformvm/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ type Config struct {
// Time of the Athens Phase network upgrade
AthensPhaseTime time.Time

// Time of the DACPhase network upgrade
DACPhaseTime time.Time

// Subnet ID --> Minimum portion of the subnet's stake this node must be
// connected to in order to report healthy.
// [constants.PrimaryNetworkID] is always a key in this map.
Expand Down Expand Up @@ -149,6 +152,10 @@ func (c *Config) IsAthensPhaseActivated(timestamp time.Time) bool {
return !timestamp.Before(c.AthensPhaseTime)
}

func (c *Config) IsSunrisePhase2Activated(timestamp time.Time) bool {
return !timestamp.Before(c.DACPhaseTime)
}

func (c *Config) GetCreateBlockchainTxFee(timestamp time.Time) uint64 {
if c.IsApricotPhase3Activated(timestamp) {
return c.CreateBlockchainTxFee
Expand Down
138 changes: 138 additions & 0 deletions vms/platformvm/dac/camino_change_base_fee_proposal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package dac

import (
"bytes"
"errors"
"time"

"github.com/ava-labs/avalanchego/ids"
"golang.org/x/exp/slices"
)

var (
_ Proposal = (*BaseFeeProposal)(nil)
_ ProposalState = (*BaseFeeProposalState)(nil)

errZeroFee = errors.New("base-fee option is zero")
errWrongOptionsCount = errors.New("wrong options count")
)

type BaseFeeProposal struct {
Options []uint64 `serialize:"true"`
Start uint64 `serialize:"true"`
End uint64 `serialize:"true"`
}

func (p *BaseFeeProposal) StartTime() time.Time {
return time.Unix(int64(p.Start), 0)
}

func (p *BaseFeeProposal) Verify() error {
switch {
case len(p.Options) > 3:
return errWrongOptionsCount
case p.Start >= p.End:
return errEndNotAfterStart
}
for _, fee := range p.Options {
if fee == 0 {
return errZeroFee
}
}
return nil
}

// Will return modified proposal allowed voters set, original proposal will not be modified!
func (p *BaseFeeProposal) CreateProposalState(allowedVoters []ids.ShortID) ProposalState {
stateProposal := &BaseFeeProposalState{
SimpleVoteOptions: SimpleVoteOptions[uint64]{
Options: make([]SimpleVoteOption[uint64], len(p.Options)),
},
Start: p.Start,
End: p.End,
AllowedVoters: allowedVoters,
SuccessThreshold: uint32(len(allowedVoters) / 2),
}
for i := range p.Options {
stateProposal.Options[i].Value = p.Options[i]
}
return stateProposal
}

func (p *BaseFeeProposal) Visit(visitor VerifierVisitor) error {
return visitor.BaseFeeProposal(p)
}

type BaseFeeProposalState struct {
SimpleVoteOptions[uint64] `serialize:"true"`

Start uint64 `serialize:"true"`
End uint64 `serialize:"true"`
AllowedVoters []ids.ShortID `serialize:"true"`
SuccessThreshold uint32 `serialize:"true"`
}

func (p *BaseFeeProposalState) StartTime() time.Time {
return time.Unix(int64(p.Start), 0)
}

func (p *BaseFeeProposalState) EndTime() time.Time {
return time.Unix(int64(p.End), 0)
}

func (p *BaseFeeProposalState) IsActiveAt(time time.Time) bool {
timestamp := uint64(time.Unix())
return p.Start <= timestamp && timestamp <= p.End
}

func (p *BaseFeeProposalState) CanBeFinished() bool {
mostVotedWeight, _, unambiguous := p.GetMostVoted()
voted := uint32(0)
for i := range p.Options {
voted += p.Options[i].Weight
}
return voted == uint32(len(p.AllowedVoters)) || (unambiguous && mostVotedWeight > p.SuccessThreshold)
}

func (p *BaseFeeProposalState) VerifyCanVote(voterAddr ids.ShortID) error {
return verifyCanVote(p.AllowedVoters, voterAddr)
}

// Votes must be valid for this proposal, could panic otherwise.
func (p *BaseFeeProposalState) Result() (uint64, uint32, bool) {
mostVotedWeight, mostVotedOptionIndex, unambiguous := p.GetMostVoted()
return p.Options[mostVotedOptionIndex].Value, mostVotedWeight, unambiguous
}

// Will return modified proposal with added vote, original proposal will not be modified!
func (p *BaseFeeProposalState) AddVote(voterAddress ids.ShortID, voteIntf Vote) (ProposalState, error) {
vote, ok := voteIntf.(*SimpleVote)
if !ok {
return nil, ErrWrongVote
}
if int(vote.OptionIndex) >= len(p.Options) {
return nil, ErrWrongVote
}
voterAddrPos, _ := slices.BinarySearchFunc(p.AllowedVoters, voterAddress, func(id, other ids.ShortID) int {
return bytes.Compare(id[:], other[:])
})
updatedProposal := &BaseFeeProposalState{
Start: p.Start,
End: p.End,
AllowedVoters: make([]ids.ShortID, voterAddrPos),
SimpleVoteOptions: SimpleVoteOptions[uint64]{
Options: make([]SimpleVoteOption[uint64], len(p.Options)),
},
}
// we can't use the same slice, cause we need to change its elements
copy(updatedProposal.AllowedVoters, p.AllowedVoters[:voterAddrPos])
updatedProposal.AllowedVoters = append(updatedProposal.AllowedVoters[:voterAddrPos], p.AllowedVoters[voterAddrPos+1:]...)
// we can't use the same slice, cause we need to change its element
copy(updatedProposal.Options, p.Options)
updatedProposal.Options[vote.OptionIndex].Weight++
return updatedProposal, nil
}

func (p *BaseFeeProposalState) Visit(visitor ExecutorVisitor) error {
return visitor.BaseFeeProposal(p)
}
Loading

0 comments on commit a4998e4

Please sign in to comment.