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 }