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 }