code.vegaprotocol.io/vega@v0.79.0/core/governance/checkpoint.go (about) 1 // Copyright (C) 2023 Gobalsky Labs Limited 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Affero General Public License as 5 // published by the Free Software Foundation, either version 3 of the 6 // License, or (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Affero General Public License for more details. 12 // 13 // You should have received a copy of the GNU Affero General Public License 14 // along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16 package governance 17 18 import ( 19 "context" 20 "time" 21 22 "code.vegaprotocol.io/vega/core/events" 23 "code.vegaprotocol.io/vega/core/execution" 24 "code.vegaprotocol.io/vega/core/liquidity/v2" 25 "code.vegaprotocol.io/vega/core/netparams" 26 "code.vegaprotocol.io/vega/core/types" 27 "code.vegaprotocol.io/vega/libs/proto" 28 "code.vegaprotocol.io/vega/libs/ptr" 29 "code.vegaprotocol.io/vega/logging" 30 "code.vegaprotocol.io/vega/protos/vega" 31 checkpointpb "code.vegaprotocol.io/vega/protos/vega/checkpoint/v1" 32 ) 33 34 type enactmentTime struct { 35 current int64 36 shouldNotVerify bool 37 cpLoad bool 38 } 39 40 func (e *Engine) Name() types.CheckpointName { 41 return types.GovernanceCheckpoint 42 } 43 44 func (e *Engine) Checkpoint() ([]byte, error) { 45 if len(e.enactedProposals) == 0 && len(e.activeProposals) == 0 { 46 return nil, nil 47 } 48 cp := &checkpointpb.Proposals{ 49 Proposals: e.getCheckpointProposals(), 50 } 51 return proto.Marshal(cp) 52 } 53 54 func (e *Engine) Load(ctx context.Context, data []byte) error { 55 cp := &checkpointpb.Proposals{} 56 if err := proto.Unmarshal(data, cp); err != nil { 57 return err 58 } 59 60 evts := make([]events.Event, 0, len(cp.Proposals)) 61 now := e.timeService.GetTimeNow() 62 minEnact, err := e.netp.GetDuration(netparams.GovernanceProposalMarketMinEnact) 63 if err != nil { 64 e.log.Panic("failed to get proposal market min enactment duration from network parameter") 65 } 66 minAuctionDuration, err := e.netp.GetDuration(netparams.MarketAuctionMinimumDuration) 67 if err != nil { 68 e.log.Panic("failed to get proposal market min auction duration from network parameter") 69 } 70 duration := minEnact 71 // we have to choose the max between minEnact and minAuctionDuration otherwise we won't be able to submit the market successfully 72 if int64(minEnact) < int64(minAuctionDuration) { 73 duration = minAuctionDuration 74 } 75 76 latestUpdateMarketProposals := map[string]*types.Proposal{} 77 updatedMarketIDs := []string{} 78 for _, p := range cp.Proposals { 79 prop, err := types.ProposalFromProto(p) 80 if err != nil { 81 return err 82 } 83 84 switch prop.Terms.Change.GetTermType() { 85 case types.ProposalTermsTypeNewMarket: 86 // before we mess around with enactment times, determine the time until enactment 87 closeTime := time.Unix(p.Terms.ClosingTimestamp, 0) 88 enactTime := time.Unix(p.Terms.EnactmentTimestamp, 0) 89 auctionDuration := enactTime.Sub(closeTime) 90 // check for successor proposals 91 toEnact := false 92 if p.Terms.EnactmentTimestamp <= now.Unix() { 93 toEnact = true 94 } 95 enct := &enactmentTime{ 96 cpLoad: true, 97 } 98 // if the proposal is for a new market it should be restored it such that it will be in opening auction 99 if toEnact { 100 prop.Terms.ClosingTimestamp = now.Unix() 101 if auctionDuration < duration { 102 prop.Terms.EnactmentTimestamp = now.Add(duration).Unix() 103 } else { 104 prop.Terms.EnactmentTimestamp = now.Add(auctionDuration).Unix() 105 } 106 enct.shouldNotVerify = true 107 } 108 enct.current = prop.Terms.EnactmentTimestamp 109 110 // handle markets that were proposed in older versions so will not have these new fields when we resubmit 111 if prop.NewMarket().Changes.LiquiditySLAParameters == nil { 112 prop.NewMarket().Changes.LiquiditySLAParameters = ptr.From(liquidity.DefaultSLAParameters) 113 } 114 if prop.NewMarket().Changes.LiquidityFeeSettings == nil { 115 prop.NewMarket().Changes.LiquidityFeeSettings = &types.LiquidityFeeSettings{Method: types.LiquidityFeeMethodMarginalCost} 116 } 117 118 toSubmit, err := e.intoToSubmit(ctx, prop, enct, true) 119 if err != nil { 120 e.log.Panic("Failed to convert proposal into market", logging.Error(err)) 121 } 122 nm := toSubmit.NewMarket() 123 err = e.markets.RestoreMarket(ctx, nm.Market()) 124 if err != nil { 125 if err == execution.ErrMarketDoesNotExist { 126 // market has been settled, network doesn't care 127 continue 128 } 129 // any other error, panic 130 e.log.Panic("failed to restore market from checkpoint", logging.Market(*nm.Market()), logging.Error(err)) 131 } 132 133 if err := e.markets.StartOpeningAuction(ctx, prop.ID); err != nil { 134 e.log.Panic("failed to start opening auction for market", logging.String("market-id", prop.ID), logging.Error(err)) 135 } 136 case types.ProposalTermsTypeUpdateMarket: 137 marketID := prop.Terms.GetUpdateMarket().MarketID 138 updatedMarketIDs = append(updatedMarketIDs, marketID) 139 last, ok := latestUpdateMarketProposals[marketID] 140 if !ok || prop.Terms.EnactmentTimestamp > last.Terms.EnactmentTimestamp { 141 latestUpdateMarketProposals[marketID] = prop 142 } 143 } 144 145 evts = append(evts, events.NewProposalEvent(ctx, *prop)) 146 e.enactedProposals = append(e.enactedProposals, &proposal{ 147 Proposal: prop, 148 }) 149 } 150 for _, v := range updatedMarketIDs { 151 p := latestUpdateMarketProposals[v] 152 mkt, _, err := e.updatedMarketFromProposal(&proposal{Proposal: p}) 153 if err != nil { 154 continue 155 } 156 e.markets.UpdateMarket(ctx, mkt) 157 } 158 159 // send events for restored proposals 160 e.broker.SendBatch(evts) 161 // @TODO ensure OnTick is called 162 return nil 163 } 164 165 func (e *Engine) isActiveMarket(marketID string) bool { 166 mktState, err := e.markets.GetMarketState(marketID) 167 // if the market is missing from the execution engine it means it's been already cancelled or settled or rejected 168 if err == types.ErrInvalidMarketID { 169 e.log.Info("not saving market proposal to checkpoint - market has already been removed", logging.String("market-id", marketID)) 170 return false 171 } 172 if mktState == types.MarketStateTradingTerminated { 173 e.log.Info("not saving market proposal to checkpoint ", logging.String("market-id", marketID), logging.String("market-state", mktState.String())) 174 return false 175 } 176 return true 177 } 178 179 func (e *Engine) getCheckpointProposals() []*vega.Proposal { 180 ret := make([]*vega.Proposal, 0, len(e.enactedProposals)) 181 182 for _, p := range e.enactedProposals { 183 switch p.Terms.Change.GetTermType() { 184 case types.ProposalTermsTypeNewMarket: 185 if !e.isActiveMarket(p.ID) { 186 continue 187 } 188 case types.ProposalTermsTypeUpdateMarket: 189 if !e.isActiveMarket(p.MarketUpdate().MarketID) { 190 continue 191 } 192 } 193 ret = append(ret, p.IntoProto()) 194 } 195 196 // we also need to include new market proposals that have passed, but have no yet been enacted 197 // this is because they will exist in the execution engine in an opening auction and should 198 // be recreated on checkpoint restore. 199 for _, p := range e.activeProposals { 200 if !p.IsPassed() { 201 continue 202 } 203 switch p.Terms.Change.GetTermType() { 204 case types.ProposalTermsTypeNewMarket: 205 if e.isActiveMarket(p.ID) { 206 ret = append(ret, p.IntoProto()) 207 } 208 } 209 } 210 211 return ret 212 }