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 4, 2023
1 parent 0c5035f commit f17eb0e
Show file tree
Hide file tree
Showing 29 changed files with 2,691 additions and 47 deletions.
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)
}
successfulProposalIDs, err := parentState.GetProposalIDsToExecute()
if err != nil {
return nil, fmt.Errorf("could not find successful proposals: %w", err)
}
if len(expiredProposalIDs) > 0 || len(successfulProposalIDs) > 0 {
finishProposalsTx, err := txBuilder.FinishProposalsTx(expiredProposalIDs, successfulProposalIDs)
if err != nil {
return nil, fmt.Errorf("could not build tx to finish expired and successful 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
}
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 SP1 network upgrade
SunrisePhase2Time 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.SunrisePhase2Time)
}

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

import (
"errors"

"github.com/ava-labs/avalanchego/utils/set"
)

var (
_ Proposal = (*ChangeBaseFeeProposal)(nil)

errZeroFee = errors.New("base-fee option is zero")
errNotUniqueOption = errors.New("not unique option")
errNoOptions = errors.New("no options")
errWrongVoteType = errors.New("wrong vote type")
)

type ChangeBaseFeeProposal struct {
ProposalWithDuration `serialize:"true"`
FeeOptions []uint64 `serialize:"true"`
}

func (p *ChangeBaseFeeProposal) Verify() error {
// TODO@ make it strictly 3 ? thats requirement - to have 3 options for this proposal
if len(p.FeeOptions) == 0 {
return errNoOptions
}

uniqueFees := set.NewSet[uint64](len(p.FeeOptions))
for _, fee := range p.FeeOptions {
if fee == 0 {
return errZeroFee
}
if uniqueFees.Contains(fee) {
return errNotUniqueOption
}
uniqueFees.Add(fee)
}

return p.ProposalWithDuration.Verify()
}

func (p *ChangeBaseFeeProposal) CanBeVotedWith(voteIntf Vote) bool {
vote, ok := voteIntf.(*SimpleVote)
if !ok {
return false
}
if int(vote.OptionIndex) >= len(p.FeeOptions) {
return false
}
return true
}

func (p *ChangeBaseFeeProposal) Visit(visitor Visitor) error {
return visitor.ChangeBaseFeeProposal(p)
}

func (p *ChangeBaseFeeProposal) canBeExecuted(proposalWithVotes *ProposalWithVotes) bool {
_, weight, err := p.Result(proposalWithVotes.Votes)
return err == nil && weight > proposalWithVotes.SuccessThreshold()
}

// Votes must be valid for this proposal, could panic otherwise.
func (p *ChangeBaseFeeProposal) Result(votes []VoteWithAddr) (uint64, uint32, error) { // TODO@ optimize
if len(votes) == 0 {
return 0, 0, errNoVotes
}

uncertain := false
optionWeights := make([]uint32, len(p.FeeOptions))
mostVotedOptionIndex := uint32(0)

for i := range votes {
vote, ok := votes[i].Vote.(*SimpleVote)
if !ok {
return 0, 0, errWrongVoteType
}
optionWeights[vote.OptionIndex]++
if vote.OptionIndex != mostVotedOptionIndex &&
optionWeights[vote.OptionIndex] == optionWeights[mostVotedOptionIndex] {
uncertain = true
} else if optionWeights[vote.OptionIndex] > optionWeights[mostVotedOptionIndex] {
mostVotedOptionIndex = vote.OptionIndex
uncertain = false
}
}

if uncertain {
return 0, 0, errUncertainResult
}

return p.FeeOptions[mostVotedOptionIndex], optionWeights[mostVotedOptionIndex], nil
}
111 changes: 111 additions & 0 deletions vms/platformvm/dao/camino_proposal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright (C) 2023, Chain4Travel AG. All rights reserved.
// See the file LICENSE for licensing terms.

package dao

import (
"errors"
"time"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/vms/components/verify"
)

const MaxProposalSize = 2048 // TODO@

var (
errEndNotAfterStart = errors.New("proposal end-time is not after start-time")
errNoVotes = errors.New("no votes")
errUncertainResult = errors.New("uncertain result")
)

type Visitor interface {
ChangeBaseFeeProposal(*ChangeBaseFeeProposal) error
}

type Proposal interface {
verify.Verifiable

CanBeVotedWith(Vote) bool
StartTime() time.Time
EndTime() time.Time
IsActiveAt(time time.Time) bool
Visit(Visitor) error

canBeExecuted(*ProposalWithVotes) bool
}

type ProposalWithVotes struct {
Proposal `serialize:"true"`
AllowedVoters []ids.ShortID `serialize:"true"` // TODO@ optimize
Votes []VoteWithAddr `serialize:"true"` // TODO@ optimize
}

func (p *ProposalWithVotes) SuccessThreshold() uint32 {
return uint32(len(p.AllowedVoters) / 2)
}

func (p *ProposalWithVotes) CanBeVotedBy(voterAddr ids.ShortID) bool { // TODO@ optimize
for _, allowedVoterAddress := range p.AllowedVoters {
if allowedVoterAddress == voterAddr {
return true
}
}
return false
}

func (p *ProposalWithVotes) WasVotedBy(voterAddr ids.ShortID) bool { // TODO@ optimize
for _, vote := range p.Votes {
if vote.VoterAddress == voterAddr {
return true
}
}
return false
}

func (p *ProposalWithVotes) CanBeExecuted() bool {
return p.Proposal.canBeExecuted(p)
}

// Will return modified proposal with added vote, original proposal will not be modified!
func (p *ProposalWithVotes) AddVote(voterAddress ids.ShortID, vote Vote) *ProposalWithVotes {
updatedProposal := *p
updatedProposal.Votes = append(p.Votes, VoteWithAddr{
Vote: vote,
VoterAddress: voterAddress,
})
return &updatedProposal
}

type TxProposalWrapper struct {
Proposal `serialize:"true"`
}

type ProposalWithStartTime struct { // some proposals will have fixed duration
Start uint64 `serialize:"true"`
}

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

type ProposalWithDuration struct { // some proposals will have flexible duration
ProposalWithStartTime `serialize:"true"`
End uint64 `serialize:"true"`
}

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

func (p *ProposalWithDuration) Verify() error {
if p.Start >= p.End {
return errEndNotAfterStart
}
return nil
}

func (p *ProposalWithDuration) IsActiveAt(time time.Time) bool {
timestamp := uint64(time.Unix())
return p.Start >= timestamp && timestamp <= p.End
}
31 changes: 31 additions & 0 deletions vms/platformvm/dao/camino_vote.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package dao

import (
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/vms/components/verify"
)

const MaxVoteSize = 2048 // TODO@

var _ Vote = (*SimpleVote)(nil)

type Vote interface {
verify.Verifiable
}

type VoteWithAddr struct {
Vote `serialize:"true"`
VoterAddress ids.ShortID `serialize:"true"`
}

type TxVoteWrapper struct {
Vote `serialize:"true"`
}

type SimpleVote struct {
OptionIndex uint32 `serialize:"true"`
}

func (*SimpleVote) Verify() error {
return nil
}
35 changes: 34 additions & 1 deletion vms/platformvm/metrics/camino_tx_metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ type caminoTxMetrics struct {
numRewardsImportTxs,
numBaseTxs,
numMultisigAliasTxs,
numAddDepositOfferTxs prometheus.Counter
numAddDepositOfferTxs,
numAddProposalTxs,
numAddVoteTxs,
numFinishProposalsTxs prometheus.Counter
}

func newCaminoTxMetrics(
Expand All @@ -46,6 +49,9 @@ func newCaminoTxMetrics(
numBaseTxs: newTxMetric(namespace, "base", registerer, &errs),
numMultisigAliasTxs: newTxMetric(namespace, "multisig_alias", registerer, &errs),
numAddDepositOfferTxs: newTxMetric(namespace, "add_deposit_offer", registerer, &errs),
numAddProposalTxs: newTxMetric(namespace, "add_proposal", registerer, &errs),
numAddVoteTxs: newTxMetric(namespace, "add_vote", registerer, &errs),
numFinishProposalsTxs: newTxMetric(namespace, "finish_proposals", registerer, &errs),
}
return m, errs.Err
}
Expand Down Expand Up @@ -88,6 +94,18 @@ func (*txMetrics) AddDepositOfferTx(*txs.AddDepositOfferTx) error {
return nil
}

func (*txMetrics) AddProposalTx(*txs.AddProposalTx) error {
return nil
}

func (*txMetrics) AddVoteTx(*txs.AddVoteTx) error {
return nil
}

func (*txMetrics) FinishProposalsTx(*txs.FinishProposalsTx) error {
return nil
}

// camino metrics

func (m *caminoTxMetrics) AddressStateTx(*txs.AddressStateTx) error {
Expand Down Expand Up @@ -134,3 +152,18 @@ func (m *caminoTxMetrics) AddDepositOfferTx(*txs.AddDepositOfferTx) error {
m.numAddDepositOfferTxs.Inc()
return nil
}

func (m *caminoTxMetrics) AddProposalTx(*txs.AddProposalTx) error {
m.numAddProposalTxs.Inc()
return nil
}

func (m *caminoTxMetrics) AddVoteTx(*txs.AddVoteTx) error {
m.numAddVoteTxs.Inc()
return nil
}

func (m *caminoTxMetrics) FinishProposalsTx(*txs.FinishProposalsTx) error {
m.numFinishProposalsTxs.Inc()
return nil
}
Loading

0 comments on commit f17eb0e

Please sign in to comment.