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  }