code.vegaprotocol.io/vega@v0.79.0/core/governance/snapshot.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  	"sort"
    21  
    22  	"code.vegaprotocol.io/vega/core/events"
    23  	"code.vegaprotocol.io/vega/core/types"
    24  	vgcontext "code.vegaprotocol.io/vega/libs/context"
    25  	"code.vegaprotocol.io/vega/libs/num"
    26  	"code.vegaprotocol.io/vega/libs/proto"
    27  	"code.vegaprotocol.io/vega/logging"
    28  	vegapb "code.vegaprotocol.io/vega/protos/vega"
    29  	snapshotpb "code.vegaprotocol.io/vega/protos/vega/snapshot/v1"
    30  
    31  	"golang.org/x/exp/maps"
    32  )
    33  
    34  var (
    35  	activeKey         = (&types.PayloadGovernanceActive{}).Key()
    36  	enactedKey        = (&types.PayloadGovernanceEnacted{}).Key()
    37  	nodeValidationKey = (&types.PayloadGovernanceNode{}).Key()
    38  	batchActiveKey    = (&types.PayloadGovernanceBatchActive{}).Key()
    39  
    40  	hashKeys = []string{
    41  		activeKey,
    42  		enactedKey,
    43  		nodeValidationKey,
    44  		batchActiveKey,
    45  	}
    46  	defaultMarkPriceConfig = &types.CompositePriceConfiguration{
    47  		DecayWeight:        num.DecimalZero(),
    48  		DecayPower:         num.DecimalZero(),
    49  		CashAmount:         num.UintZero(),
    50  		CompositePriceType: types.CompositePriceTypeByLastTrade,
    51  	}
    52  )
    53  
    54  type governanceSnapshotState struct {
    55  	serialisedActive         []byte
    56  	serialisedEnacted        []byte
    57  	serialisedNodeValidation []byte
    58  	serialisedBatchActive    []byte
    59  }
    60  
    61  func (e *Engine) OnStateLoaded(ctx context.Context) error {
    62  	// previously new market proposals that passed but where not enacted existed in both
    63  	// the active and enacted slices, but now this has changed and it is only ever in one
    64  	// or the other.
    65  
    66  	// so for upgrade purposes any active proposals in the enacted slice needs to be removed
    67  	// from the enacted slice
    68  	for _, p := range e.activeProposals {
    69  		for i := range e.enactedProposals {
    70  			if p.ID == e.enactedProposals[i].ID {
    71  				e.log.Warn("removing proposal from enacted since it is also in active", logging.String("id", p.ID))
    72  				e.enactedProposals = append(e.enactedProposals[:i], e.enactedProposals[i+1:]...)
    73  				break
    74  			}
    75  		}
    76  	}
    77  
    78  	// update market events may require updating to set the liquidation strategy slippage
    79  	if vgcontext.InProgressUpgradeFromMultiple(ctx, "v0.75.8", "v0.75.7") {
    80  		evts := make([]events.Event, 0, len(e.activeProposals)/2)
    81  		for _, p := range e.activeProposals {
    82  			if !p.Proposal.IsMarketUpdate() {
    83  				continue
    84  			}
    85  			mID := p.Proposal.MarketUpdate().MarketID
    86  			changes := p.Proposal.MarketUpdate().Changes
    87  			if changes.LiquidationStrategy != nil && changes.LiquidationStrategy.DisposalSlippage.IsZero() {
    88  				existingMarket, ok := e.markets.GetMarket(mID, false)
    89  				if !ok {
    90  					continue
    91  				}
    92  				// execution engine has already been restored at this point, so we can get the current slippage value from the market itself.
    93  				changes.LiquidationStrategy.DisposalSlippage = existingMarket.LiquidationStrategy.DisposalSlippage
    94  				evts = append(evts, events.NewProposalEvent(ctx, *p.Proposal))
    95  			}
    96  		}
    97  		if len(evts) > 0 {
    98  			e.broker.SendBatch(evts)
    99  		}
   100  	}
   101  
   102  	return nil
   103  }
   104  
   105  // serialiseActiveProposals returns the engine's active proposals as marshalled bytes.
   106  func (e *Engine) serialiseActiveProposals() ([]byte, error) {
   107  	pending := make([]*types.ProposalData, 0, len(e.activeProposals))
   108  	for _, p := range e.activeProposals {
   109  		pp := &types.ProposalData{
   110  			Proposal: p.Proposal,
   111  			Yes:      votesAsSlice(p.yes),
   112  			No:       votesAsSlice(p.no),
   113  			Invalid:  votesAsSlice(p.invalidVotes),
   114  		}
   115  		pending = append(pending, pp)
   116  	}
   117  
   118  	pl := types.Payload{
   119  		Data: &types.PayloadGovernanceActive{
   120  			GovernanceActive: &types.GovernanceActive{
   121  				Proposals: pending,
   122  			},
   123  		},
   124  	}
   125  
   126  	return proto.Marshal(pl.IntoProto())
   127  }
   128  
   129  // serialiseBatchActiveProposals returns the engine's batch active proposals as marshalled bytes.
   130  func (e *Engine) serialiseBatchActiveProposals() ([]byte, error) {
   131  	batchIDs := maps.Keys(e.activeBatchProposals)
   132  	sort.Strings(batchIDs)
   133  
   134  	batchProposals := make([]*snapshotpb.BatchProposalData, 0, len(batchIDs))
   135  	for _, batchID := range batchIDs {
   136  		batchProposal := e.activeBatchProposals[batchID]
   137  
   138  		bpd := &snapshotpb.BatchProposalData{
   139  			BatchProposal: &snapshotpb.ProposalData{
   140  				Proposal: batchProposal.ToProto(),
   141  				Yes:      votesAsProtoSlice(batchProposal.yes),
   142  				No:       votesAsProtoSlice(batchProposal.no),
   143  				Invalid:  votesAsProtoSlice(batchProposal.invalidVotes),
   144  			},
   145  			Proposals: make([]*vegapb.Proposal, 0, len(batchProposal.Proposals)),
   146  		}
   147  
   148  		for _, proposal := range batchProposal.Proposals {
   149  			bpd.Proposals = append(bpd.Proposals, proposal.IntoProto())
   150  		}
   151  
   152  		batchProposals = append(batchProposals, bpd)
   153  	}
   154  
   155  	pl := types.Payload{
   156  		Data: &types.PayloadGovernanceBatchActive{
   157  			GovernanceBatchActive: &types.GovernanceBatchActive{
   158  				BatchProposals: batchProposals,
   159  			},
   160  		},
   161  	}
   162  
   163  	return proto.Marshal(pl.IntoProto())
   164  }
   165  
   166  // serialiseEnactedProposals returns the engine's enacted proposals as marshalled bytes.
   167  func (e *Engine) serialiseEnactedProposals() ([]byte, error) {
   168  	enacted := make([]*types.ProposalData, 0, len(e.activeProposals))
   169  	for _, p := range e.enactedProposals {
   170  		pp := &types.ProposalData{
   171  			Proposal: p.Proposal,
   172  			Yes:      votesAsSlice(p.yes),
   173  			No:       votesAsSlice(p.no),
   174  			Invalid:  votesAsSlice(p.invalidVotes),
   175  		}
   176  		enacted = append(enacted, pp)
   177  	}
   178  
   179  	pl := types.Payload{
   180  		Data: &types.PayloadGovernanceEnacted{
   181  			GovernanceEnacted: &types.GovernanceEnacted{
   182  				Proposals: enacted,
   183  			},
   184  		},
   185  	}
   186  	return proto.Marshal(pl.IntoProto())
   187  }
   188  
   189  // serialiseNodeProposals returns the engine's proposals waiting for node validation.
   190  func (e *Engine) serialiseNodeProposals() ([]byte, error) {
   191  	nodeProposals := e.nodeProposalValidation.getProposals()
   192  	nodeBatchProposals := e.nodeProposalValidation.getBatchProposals()
   193  	proposals := make([]*types.ProposalData, 0, len(nodeProposals))
   194  	batchProposals := make([]*snapshotpb.BatchProposalData, 0, len(nodeBatchProposals))
   195  
   196  	for _, np := range nodeProposals {
   197  		proposals = append(proposals, &types.ProposalData{
   198  			Proposal: np.Proposal,
   199  			Yes:      votesAsSlice(np.yes),
   200  			No:       votesAsSlice(np.no),
   201  			Invalid:  votesAsSlice(np.invalidVotes),
   202  		})
   203  	}
   204  
   205  	for _, proposal := range nodeBatchProposals {
   206  		bp := &snapshotpb.BatchProposalData{
   207  			BatchProposal: &snapshotpb.ProposalData{
   208  				Proposal: proposal.ToProto(),
   209  				Yes:      votesAsProtoSlice(proposal.yes),
   210  				No:       votesAsProtoSlice(proposal.no),
   211  				Invalid:  votesAsProtoSlice(proposal.invalidVotes),
   212  			},
   213  			Proposals: make([]*vegapb.Proposal, 0, len(proposal.Proposals)),
   214  		}
   215  
   216  		for _, proposal := range proposal.Proposals {
   217  			bp.Proposals = append(bp.Proposals, proposal.IntoProto())
   218  		}
   219  
   220  		batchProposals = append(batchProposals, bp)
   221  	}
   222  
   223  	pl := types.Payload{
   224  		Data: &types.PayloadGovernanceNode{
   225  			GovernanceNode: &types.GovernanceNode{
   226  				ProposalData:      proposals,
   227  				BatchProposalData: batchProposals,
   228  			},
   229  		},
   230  	}
   231  	return proto.Marshal(pl.IntoProto())
   232  }
   233  
   234  func (e *Engine) serialiseK(serialFunc func() ([]byte, error), dataField *[]byte) ([]byte, error) {
   235  	data, err := serialFunc()
   236  	if err != nil {
   237  		return nil, err
   238  	}
   239  	*dataField = data
   240  	return data, nil
   241  }
   242  
   243  func (e *Engine) serialise(k string) ([]byte, error) {
   244  	switch k {
   245  	case activeKey:
   246  		return e.serialiseK(e.serialiseActiveProposals, &e.gss.serialisedActive)
   247  	case enactedKey:
   248  		return e.serialiseK(e.serialiseEnactedProposals, &e.gss.serialisedEnacted)
   249  	case nodeValidationKey:
   250  		return e.serialiseK(e.serialiseNodeProposals, &e.gss.serialisedNodeValidation)
   251  	case batchActiveKey:
   252  		return e.serialiseK(e.serialiseBatchActiveProposals, &e.gss.serialisedBatchActive)
   253  	default:
   254  		return nil, types.ErrSnapshotKeyDoesNotExist
   255  	}
   256  }
   257  
   258  func (e *Engine) Namespace() types.SnapshotNamespace {
   259  	return types.GovernanceSnapshot
   260  }
   261  
   262  func (e *Engine) Keys() []string {
   263  	return hashKeys
   264  }
   265  
   266  func (e *Engine) Stopped() bool {
   267  	return false
   268  }
   269  
   270  func (e *Engine) GetState(k string) ([]byte, []types.StateProvider, error) {
   271  	data, err := e.serialise(k)
   272  	return data, nil, err
   273  }
   274  
   275  func (e *Engine) LoadState(ctx context.Context, p *types.Payload) ([]types.StateProvider, error) {
   276  	if e.Namespace() != p.Data.Namespace() {
   277  		return nil, types.ErrInvalidSnapshotNamespace
   278  	}
   279  
   280  	switch pl := p.Data.(type) {
   281  	case *types.PayloadGovernanceActive:
   282  		return nil, e.restoreActiveProposals(ctx, pl.GovernanceActive, p)
   283  	case *types.PayloadGovernanceEnacted:
   284  		e.restoreEnactedProposals(ctx, pl.GovernanceEnacted, p)
   285  		return nil, nil
   286  	case *types.PayloadGovernanceNode:
   287  		return nil, e.restoreNodeProposals(ctx, pl.GovernanceNode, p)
   288  	case *types.PayloadGovernanceBatchActive:
   289  		return nil, e.restoreBatchActiveProposals(ctx, pl.GovernanceBatchActive, p)
   290  	default:
   291  		return nil, types.ErrUnknownSnapshotType
   292  	}
   293  }
   294  
   295  func (e *Engine) restoreActiveProposals(ctx context.Context, active *types.GovernanceActive, p *types.Payload) error {
   296  	e.activeProposals = make([]*proposal, 0, len(active.Proposals))
   297  	evts := []events.Event{}
   298  	vevts := []events.Event{}
   299  	e.log.Debug("restoring active proposals snapshot", logging.Int("nproposals", len(active.Proposals)))
   300  	for _, p := range active.Proposals {
   301  		if vgcontext.InProgressUpgradeFromMultiple(ctx, "v0.75.8", "v0.75.7") {
   302  			if p.Proposal.IsNewMarket() || p.Proposal.IsMarketUpdate() {
   303  				setLiquidationSlippage(p.Proposal)
   304  			}
   305  		}
   306  		pp := &proposal{
   307  			Proposal:     p.Proposal,
   308  			yes:          votesAsMap(p.Yes),
   309  			no:           votesAsMap(p.No),
   310  			invalidVotes: votesAsMap(p.Invalid),
   311  		}
   312  
   313  		e.log.Debug("proposals",
   314  			logging.String("id", pp.ID),
   315  			logging.Int("yes", len(pp.yes)),
   316  			logging.Int("no", len(pp.no)),
   317  			logging.Int("invalid", len(pp.invalidVotes)),
   318  		)
   319  		e.activeProposals = append(e.activeProposals, pp)
   320  		evts = append(evts, events.NewProposalEvent(ctx, *pp.Proposal))
   321  
   322  		for _, v := range pp.yes {
   323  			vevts = append(vevts, events.NewVoteEvent(ctx, *v))
   324  		}
   325  		for _, v := range pp.no {
   326  			vevts = append(vevts, events.NewVoteEvent(ctx, *v))
   327  		}
   328  
   329  		for _, v := range pp.invalidVotes {
   330  			vevts = append(vevts, events.NewVoteEvent(ctx, *v))
   331  		}
   332  	}
   333  
   334  	var err error
   335  	e.gss.serialisedActive, err = proto.Marshal(p.IntoProto())
   336  	e.broker.SendBatch(evts)
   337  	e.broker.SendBatch(vevts)
   338  	return err
   339  }
   340  
   341  func setLiquidationSlippage(p *types.Proposal) {
   342  	if p.IsNewMarket() {
   343  		if !p.NewMarket().Changes.LiquidationStrategy.DisposalSlippage.IsZero() {
   344  			return
   345  		}
   346  		changes := p.NewMarket().Changes
   347  		changes.LiquidationStrategy.DisposalSlippage = changes.LiquiditySLAParameters.PriceRange
   348  		return
   349  	}
   350  	// this must be a market update
   351  	changes := p.MarketUpdate().Changes
   352  	if changes.LiquidationStrategy != nil && changes.LiquiditySLAParameters != nil && changes.LiquidationStrategy.DisposalSlippage.IsZero() {
   353  		changes.LiquidationStrategy.DisposalSlippage = changes.LiquiditySLAParameters.PriceRange
   354  	}
   355  }
   356  
   357  func (e *Engine) restoreBatchActiveProposals(ctx context.Context, active *types.GovernanceBatchActive, p *types.Payload) error {
   358  	e.activeBatchProposals = make(map[string]*batchProposal, len(active.BatchProposals))
   359  
   360  	evts := []events.Event{}
   361  	vevts := []events.Event{}
   362  	e.log.Debug("restoring active proposals snapshot", logging.Int("nproposals", len(active.BatchProposals)))
   363  	for _, bpp := range active.BatchProposals {
   364  		bpt := types.BatchProposalFromSnapshotProto(bpp.BatchProposal.Proposal, bpp.Proposals)
   365  		bp := &batchProposal{
   366  			BatchProposal: bpt,
   367  			yes:           votesAsMapFromProto(bpp.BatchProposal.Yes),
   368  			no:            votesAsMapFromProto(bpp.BatchProposal.No),
   369  			invalidVotes:  votesAsMapFromProto(bpp.BatchProposal.Invalid),
   370  		}
   371  
   372  		evts = append(evts, events.NewProposalEventFromProto(ctx, bp.BatchProposal.ToProto()))
   373  		for _, p := range bp.BatchProposal.Proposals {
   374  			if vgcontext.InProgressUpgradeFromMultiple(ctx, "v0.75.8", "v0.75.7") {
   375  				if p.IsMarketUpdate() || p.IsNewMarket() {
   376  					setLiquidationSlippage(p)
   377  				}
   378  			}
   379  			evts = append(evts, events.NewProposalEvent(ctx, *p))
   380  		}
   381  
   382  		e.log.Debug("batch proposal",
   383  			logging.String("id", bp.ID),
   384  			logging.Int("yes", len(bp.yes)),
   385  			logging.Int("no", len(bp.no)),
   386  			logging.Int("invalid", len(bp.invalidVotes)),
   387  		)
   388  
   389  		e.activeBatchProposals[bp.ID] = bp
   390  
   391  		for _, v := range bp.yes {
   392  			vevts = append(vevts, events.NewVoteEvent(ctx, *v))
   393  		}
   394  		for _, v := range bp.no {
   395  			vevts = append(vevts, events.NewVoteEvent(ctx, *v))
   396  		}
   397  
   398  		for _, v := range bp.invalidVotes {
   399  			vevts = append(vevts, events.NewVoteEvent(ctx, *v))
   400  		}
   401  	}
   402  
   403  	var err error
   404  	e.gss.serialisedBatchActive, err = proto.Marshal(p.IntoProto())
   405  	e.broker.SendBatch(evts)
   406  	e.broker.SendBatch(vevts)
   407  	return err
   408  }
   409  
   410  func (e *Engine) restoreEnactedProposals(ctx context.Context, enacted *types.GovernanceEnacted, p *types.Payload) {
   411  	evts := []events.Event{}
   412  	vevts := []events.Event{}
   413  	e.log.Debug("restoring enacted proposals snapshot", logging.Int("nproposals", len(enacted.Proposals)))
   414  	for _, p := range enacted.Proposals {
   415  		pp := &proposal{
   416  			Proposal:     p.Proposal,
   417  			yes:          votesAsMap(p.Yes),
   418  			no:           votesAsMap(p.No),
   419  			invalidVotes: votesAsMap(p.Invalid),
   420  		}
   421  		e.log.Debug("proposals",
   422  			logging.String("id", pp.ID),
   423  			logging.Int("yes", len(pp.yes)),
   424  			logging.Int("no", len(pp.no)),
   425  			logging.Int("invalid", len(pp.invalidVotes)),
   426  		)
   427  		e.enactedProposals = append(e.enactedProposals, pp)
   428  		evts = append(evts, events.NewProposalEvent(ctx, *pp.Proposal))
   429  
   430  		for _, v := range pp.yes {
   431  			vevts = append(vevts, events.NewVoteEvent(ctx, *v))
   432  		}
   433  		for _, v := range pp.no {
   434  			vevts = append(vevts, events.NewVoteEvent(ctx, *v))
   435  		}
   436  
   437  		for _, v := range pp.invalidVotes {
   438  			vevts = append(vevts, events.NewVoteEvent(ctx, *v))
   439  		}
   440  	}
   441  	e.gss.serialisedEnacted, _ = proto.Marshal(p.IntoProto())
   442  	e.broker.SendBatch(evts)
   443  	e.broker.SendBatch(vevts)
   444  }
   445  
   446  func (e *Engine) restoreNodeProposals(ctx context.Context, node *types.GovernanceNode, p *types.Payload) error {
   447  	// node.Proposals should be empty for new snapshots because they are the old version that didn't include votes
   448  	for _, p := range node.Proposals {
   449  		e.nodeProposalValidation.restore(ctx, &types.ProposalData{Proposal: p})
   450  		e.broker.Send(events.NewProposalEvent(ctx, *p))
   451  	}
   452  
   453  	for _, p := range node.ProposalData {
   454  		e.nodeProposalValidation.restore(ctx, p)
   455  		e.broker.Send(events.NewProposalEvent(ctx, *p.Proposal))
   456  	}
   457  
   458  	for _, p := range node.BatchProposalData {
   459  		prop, _ := e.nodeProposalValidation.restoreBatch(ctx, p)
   460  		e.broker.Send(events.NewProposalEventFromProto(ctx, prop.ToProto()))
   461  	}
   462  
   463  	var err error
   464  	e.gss.serialisedNodeValidation, err = proto.Marshal(p.IntoProto())
   465  	return err
   466  }
   467  
   468  // votesAsSlice returns a sorted slice of votes from a given map of votes.
   469  func votesAsSlice(votes map[string]*types.Vote) []*types.Vote {
   470  	ret := make([]*types.Vote, 0, len(votes))
   471  	for _, v := range votes {
   472  		ret = append(ret, v)
   473  	}
   474  	sort.SliceStable(ret, func(i, j int) bool { return ret[i].PartyID < ret[j].PartyID })
   475  	return ret
   476  }
   477  
   478  // votesAsProtoSlice returns a sorted slice of proto votes from a given map of votes.
   479  func votesAsProtoSlice(votes map[string]*types.Vote) []*vegapb.Vote {
   480  	ret := make([]*vegapb.Vote, 0, len(votes))
   481  	for _, v := range votes {
   482  		ret = append(ret, v.IntoProto())
   483  	}
   484  	sort.SliceStable(ret, func(i, j int) bool { return ret[i].PartyId < ret[j].PartyId })
   485  	return ret
   486  }
   487  
   488  // votesAsMap returns an partyID => Vote map from the given slice of votes.
   489  func votesAsMap(votes []*types.Vote) map[string]*types.Vote {
   490  	r := make(map[string]*types.Vote, len(votes))
   491  	for _, v := range votes {
   492  		r[v.PartyID] = v
   493  	}
   494  	return r
   495  }
   496  
   497  // votesAsMapFromProto returns an partyID => Vote map from the given slice of proto votes.
   498  func votesAsMapFromProto(votes []*vegapb.Vote) map[string]*types.Vote {
   499  	r := make(map[string]*types.Vote, len(votes))
   500  	for _, v := range votes {
   501  		v, _ := types.VoteFromProto(v)
   502  		r[v.PartyID] = v
   503  	}
   504  	return r
   505  }