code.vegaprotocol.io/vega@v0.79.0/core/teams/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 teams
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"strings"
    22  
    23  	"code.vegaprotocol.io/vega/core/types"
    24  	"code.vegaprotocol.io/vega/libs/proto"
    25  	snapshotpb "code.vegaprotocol.io/vega/protos/vega/snapshot/v1"
    26  
    27  	"golang.org/x/exp/slices"
    28  )
    29  
    30  type SnapshottedEngine struct {
    31  	*Engine
    32  
    33  	pl types.Payload
    34  
    35  	stopped bool
    36  
    37  	// Keys need to be computed when the engine is instantiated as they are dynamic.
    38  	hashKeys        []string
    39  	teamsKey        string
    40  	teamSwitchesKey string
    41  }
    42  
    43  func (e *SnapshottedEngine) Namespace() types.SnapshotNamespace {
    44  	return types.TeamsSnapshot
    45  }
    46  
    47  func (e *SnapshottedEngine) Keys() []string {
    48  	return e.hashKeys
    49  }
    50  
    51  func (e *SnapshottedEngine) GetState(k string) ([]byte, []types.StateProvider, error) {
    52  	state, err := e.serialise(k)
    53  	return state, nil, err
    54  }
    55  
    56  func (e *SnapshottedEngine) LoadState(_ context.Context, p *types.Payload) ([]types.StateProvider, error) {
    57  	if e.Namespace() != p.Data.Namespace() {
    58  		return nil, types.ErrInvalidSnapshotNamespace
    59  	}
    60  
    61  	switch data := p.Data.(type) {
    62  	case *types.PayloadTeams:
    63  		e.Engine.loadTeamsFromSnapshot(data.Teams)
    64  		return nil, nil
    65  	case *types.PayloadTeamSwitches:
    66  		e.Engine.loadTeamSwitchesFromSnapshot(data.TeamSwitches)
    67  		return nil, nil
    68  	default:
    69  		return nil, types.ErrUnknownSnapshotType
    70  	}
    71  }
    72  
    73  func (e *SnapshottedEngine) Stopped() bool {
    74  	return e.stopped
    75  }
    76  
    77  func (e *SnapshottedEngine) StopSnapshots() {
    78  	e.stopped = true
    79  }
    80  
    81  func (e *SnapshottedEngine) serialise(k string) ([]byte, error) {
    82  	if e.stopped {
    83  		return nil, nil
    84  	}
    85  
    86  	switch k {
    87  	case e.teamsKey:
    88  		return e.serialiseTeams()
    89  	case e.teamSwitchesKey:
    90  		return e.serialiseTeamSwitches()
    91  	default:
    92  		return nil, types.ErrSnapshotKeyDoesNotExist
    93  	}
    94  }
    95  
    96  func (e *SnapshottedEngine) serialiseTeams() ([]byte, error) {
    97  	teams := e.Engine.teams
    98  	teamsSnapshot := make([]*snapshotpb.Team, 0, len(teams))
    99  	for _, team := range teams {
   100  		refereesSnapshot := make([]*snapshotpb.Membership, 0, len(team.Referees))
   101  		for _, referee := range team.Referees {
   102  			refereesSnapshot = append(refereesSnapshot, &snapshotpb.Membership{
   103  				PartyId:        string(referee.PartyID),
   104  				JoinedAt:       referee.JoinedAt.UnixNano(),
   105  				StartedAtEpoch: referee.StartedAtEpoch,
   106  			})
   107  		}
   108  
   109  		teamSnapshot := &snapshotpb.Team{
   110  			Id: string(team.ID),
   111  			Referrer: &snapshotpb.Membership{
   112  				PartyId:        string(team.Referrer.PartyID),
   113  				JoinedAt:       team.Referrer.JoinedAt.UnixNano(),
   114  				StartedAtEpoch: team.Referrer.StartedAtEpoch,
   115  			},
   116  			Referees:  refereesSnapshot,
   117  			Name:      team.Name,
   118  			TeamUrl:   team.TeamURL,
   119  			AvatarUrl: team.AvatarURL,
   120  			CreatedAt: team.CreatedAt.UnixNano(),
   121  			Closed:    team.Closed,
   122  		}
   123  
   124  		if len(team.AllowList) > 0 {
   125  			teamSnapshot.AllowList = make([]string, 0, len(team.AllowList))
   126  			for _, partyID := range team.AllowList {
   127  				teamSnapshot.AllowList = append(teamSnapshot.AllowList, partyID.String())
   128  			}
   129  		}
   130  
   131  		teamsSnapshot = append(teamsSnapshot, teamSnapshot)
   132  	}
   133  
   134  	slices.SortStableFunc(teamsSnapshot, func(a, b *snapshotpb.Team) int {
   135  		return strings.Compare(a.Id, b.Id)
   136  	})
   137  
   138  	payload := &snapshotpb.Payload{
   139  		Data: &snapshotpb.Payload_Teams{
   140  			Teams: &snapshotpb.Teams{
   141  				Teams: teamsSnapshot,
   142  			},
   143  		},
   144  	}
   145  
   146  	serialisedTeams, err := proto.Marshal(payload)
   147  	if err != nil {
   148  		return nil, fmt.Errorf("could not serialize teams payload: %w", err)
   149  	}
   150  
   151  	return serialisedTeams, nil
   152  }
   153  
   154  func (e *SnapshottedEngine) serialiseTeamSwitches() ([]byte, error) {
   155  	teamSwitches := e.Engine.teamSwitches
   156  	teamSwitchesSnapshot := make([]*snapshotpb.TeamSwitch, 0, len(teamSwitches))
   157  
   158  	for partyID, teamSwitch := range teamSwitches {
   159  		teamSwitchSnapshot := &snapshotpb.TeamSwitch{
   160  			FromTeamId: string(teamSwitch.fromTeam),
   161  			ToTeamId:   string(teamSwitch.toTeam),
   162  			PartyId:    string(partyID),
   163  		}
   164  		teamSwitchesSnapshot = append(teamSwitchesSnapshot, teamSwitchSnapshot)
   165  	}
   166  
   167  	slices.SortStableFunc(teamSwitchesSnapshot, func(a, b *snapshotpb.TeamSwitch) int {
   168  		return strings.Compare(a.PartyId, b.PartyId)
   169  	})
   170  
   171  	payload := &snapshotpb.Payload{
   172  		Data: &snapshotpb.Payload_TeamSwitches{
   173  			TeamSwitches: &snapshotpb.TeamSwitches{
   174  				TeamSwitches: teamSwitchesSnapshot,
   175  			},
   176  		},
   177  	}
   178  
   179  	serialisedTeamSwitches, err := proto.Marshal(payload)
   180  	if err != nil {
   181  		return nil, fmt.Errorf("could not serialize team switches payload: %w", err)
   182  	}
   183  
   184  	return serialisedTeamSwitches, nil
   185  }
   186  
   187  func (e *SnapshottedEngine) buildHashKeys() {
   188  	e.teamsKey = (&types.PayloadTeams{}).Key()
   189  	e.teamSwitchesKey = (&types.PayloadTeamSwitches{}).Key()
   190  
   191  	e.hashKeys = append([]string{}, e.teamsKey, e.teamSwitchesKey)
   192  }
   193  
   194  func NewSnapshottedEngine(broker Broker, timeSvc TimeService) *SnapshottedEngine {
   195  	se := &SnapshottedEngine{
   196  		Engine:  NewEngine(broker, timeSvc),
   197  		pl:      types.Payload{},
   198  		stopped: false,
   199  	}
   200  
   201  	se.buildHashKeys()
   202  
   203  	return se
   204  }