github.com/decred/politeia@v1.4.0/politeiawww/legacy/ticketvote/process.go (about) 1 // Copyright (c) 2020-2021 The Decred developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package ticketvote 6 7 import ( 8 "context" 9 "fmt" 10 11 "github.com/decred/politeia/politeiad/plugins/ticketvote" 12 v1 "github.com/decred/politeia/politeiawww/api/ticketvote/v1" 13 "github.com/decred/politeia/politeiawww/legacy/user" 14 ) 15 16 func (t *TicketVote) processAuthorize(ctx context.Context, a v1.Authorize, u user.User) (*v1.AuthorizeReply, error) { 17 log.Tracef("processAuthorize: %v", a.Token) 18 19 // Verify user signed with their active identity 20 if u.PublicKey() != a.PublicKey { 21 return nil, v1.UserErrorReply{ 22 ErrorCode: v1.ErrorCodePublicKeyInvalid, 23 ErrorContext: "not active identity", 24 } 25 } 26 27 // Verify user is the record author 28 authorID, err := t.politeiad.Author(ctx, a.Token) 29 if err != nil { 30 return nil, err 31 } 32 if u.ID.String() != authorID { 33 return nil, v1.UserErrorReply{ 34 ErrorCode: v1.ErrorCodeUnauthorized, 35 ErrorContext: "user is not record author", 36 } 37 } 38 39 // Send plugin command 40 ta := ticketvote.Authorize{ 41 Token: a.Token, 42 Version: a.Version, 43 Action: ticketvote.AuthActionT(a.Action), 44 PublicKey: a.PublicKey, 45 Signature: a.Signature, 46 } 47 tar, err := t.politeiad.TicketVoteAuthorize(ctx, ta) 48 if err != nil { 49 return nil, err 50 } 51 52 // Emit event 53 t.events.Emit(EventTypeAuthorize, 54 EventAuthorize{ 55 Auth: a, 56 User: u, 57 }) 58 59 return &v1.AuthorizeReply{ 60 Timestamp: tar.Timestamp, 61 Receipt: tar.Receipt, 62 }, nil 63 } 64 65 func (t *TicketVote) processStart(ctx context.Context, s v1.Start, u user.User) (*v1.StartReply, error) { 66 log.Tracef("processStart: %v", len(s.Starts)) 67 68 // Verify there is work to be done 69 if len(s.Starts) == 0 { 70 return nil, v1.UserErrorReply{ 71 ErrorCode: v1.ErrorCodeInputInvalid, 72 ErrorContext: "no start details found", 73 } 74 } 75 76 // Verify user signed with their active identity 77 for _, v := range s.Starts { 78 if u.PublicKey() != v.PublicKey { 79 return nil, v1.UserErrorReply{ 80 ErrorCode: v1.ErrorCodePublicKeyInvalid, 81 ErrorContext: "not active identity", 82 } 83 } 84 } 85 86 // Get token from start details 87 var token string 88 for _, v := range s.Starts { 89 switch v.Params.Type { 90 case v1.VoteTypeRunoff: 91 // This is a runoff vote. Execute the plugin command on the 92 // parent record. 93 token = v.Params.Parent 94 case v1.VoteTypeStandard: 95 // This is a standard vote. Execute the plugin command on the 96 // record specified in the vote params. 97 token = v.Params.Token 98 } 99 } 100 101 // Send plugin command 102 ts := convertStartToPlugin(s) 103 tsr, err := t.politeiad.TicketVoteStart(ctx, token, ts) 104 if err != nil { 105 return nil, err 106 } 107 108 // Emit notification for each start 109 t.events.Emit(EventTypeStart, 110 EventStart{ 111 Starts: s.Starts, 112 User: u, 113 }) 114 115 return &v1.StartReply{ 116 Receipt: tsr.Receipt, 117 StartBlockHeight: tsr.StartBlockHeight, 118 StartBlockHash: tsr.StartBlockHash, 119 EndBlockHeight: tsr.EndBlockHeight, 120 EligibleTickets: tsr.EligibleTickets, 121 }, nil 122 } 123 124 func (t *TicketVote) processCastBallot(ctx context.Context, cb v1.CastBallot) (*v1.CastBallotReply, error) { 125 log.Tracef("processCastBallot") 126 127 // Get token from one of the votes 128 var token string 129 for _, v := range cb.Votes { 130 token = v.Token 131 break 132 } 133 134 // Send plugin command 135 tcb := ticketvote.CastBallot{ 136 Ballot: convertCastVotesToPlugin(cb.Votes), 137 } 138 tcbr, err := t.politeiad.TicketVoteCastBallot(ctx, token, tcb) 139 if err != nil { 140 return nil, err 141 } 142 143 return &v1.CastBallotReply{ 144 Receipts: convertCastVoteRepliesToV1(tcbr.Receipts), 145 }, nil 146 } 147 148 func (t *TicketVote) processDetails(ctx context.Context, d v1.Details) (*v1.DetailsReply, error) { 149 log.Tracef("processsDetails: %v", d.Token) 150 151 tdr, err := t.politeiad.TicketVoteDetails(ctx, d.Token) 152 if err != nil { 153 return nil, err 154 } 155 156 var vote *v1.VoteDetails 157 if tdr.Vote != nil { 158 vd := convertVoteDetailsToV1(*tdr.Vote) 159 vote = &vd 160 } 161 162 return &v1.DetailsReply{ 163 Auths: convertAuthDetailsToV1(tdr.Auths), 164 Vote: vote, 165 }, nil 166 } 167 168 func (t *TicketVote) processResults(ctx context.Context, r v1.Results) (*v1.ResultsReply, error) { 169 log.Tracef("processResults: %v", r.Token) 170 171 rr, err := t.politeiad.TicketVoteResults(ctx, r.Token) 172 if err != nil { 173 return nil, err 174 } 175 176 return &v1.ResultsReply{ 177 Votes: convertCastVoteDetailsToV1(rr.Votes), 178 }, nil 179 } 180 181 func (t *TicketVote) processSummaries(ctx context.Context, s v1.Summaries) (*v1.SummariesReply, error) { 182 log.Tracef("processSummaries: %v", s.Tokens) 183 184 // Verify request size 185 if len(s.Tokens) > int(t.policy.SummariesPageSize) { 186 return nil, v1.UserErrorReply{ 187 ErrorCode: v1.ErrorCodePageSizeExceeded, 188 ErrorContext: fmt.Sprintf("max page size is %v", 189 t.policy.SummariesPageSize), 190 } 191 } 192 193 // Get vote summaries 194 ts, err := t.politeiad.TicketVoteSummaries(ctx, s.Tokens) 195 if err != nil { 196 return nil, err 197 } 198 199 return &v1.SummariesReply{ 200 Summaries: convertSummariesToV1(ts), 201 }, nil 202 } 203 204 func (t *TicketVote) processSubmissions(ctx context.Context, s v1.Submissions) (*v1.SubmissionsReply, error) { 205 log.Tracef("processSubmissions: %v", s.Token) 206 207 subs, err := t.politeiad.TicketVoteSubmissions(ctx, s.Token) 208 if err != nil { 209 return nil, err 210 } 211 212 return &v1.SubmissionsReply{ 213 Submissions: subs, 214 }, nil 215 } 216 217 func (t *TicketVote) processInventory(ctx context.Context, i v1.Inventory) (*v1.InventoryReply, error) { 218 log.Tracef("processInventory: %v %v", i.Status, i.Page) 219 220 // Get inventory 221 ti := ticketvote.Inventory{ 222 Status: convertVoteStatusToPlugin(i.Status), 223 Page: i.Page, 224 } 225 ir, err := t.politeiad.TicketVoteInventory(ctx, ti) 226 if err != nil { 227 return nil, err 228 } 229 230 return &v1.InventoryReply{ 231 Vetted: ir.Tokens, 232 BestBlock: ir.BestBlock, 233 }, nil 234 } 235 236 func (t *TicketVote) processTimestamps(ctx context.Context, ts v1.Timestamps) (*v1.TimestampsReply, error) { 237 log.Tracef("processTimestamps: %v %v", ts.Token, ts.VotesPage) 238 239 // Send plugin command 240 tt := ticketvote.Timestamps{ 241 VotesPage: ts.VotesPage, 242 } 243 tsr, err := t.politeiad.TicketVoteTimestamps(ctx, ts.Token, tt) 244 if err != nil { 245 return nil, err 246 } 247 248 // Prepare reply 249 var ( 250 auths = make([]v1.Timestamp, 0, len(tsr.Auths)) 251 votes = make([]v1.Timestamp, 0, len(tsr.Votes)) 252 253 details *v1.Timestamp 254 ) 255 if tsr.Details != nil { 256 dt := convertTimestampToV1(*tsr.Details) 257 details = &dt 258 } 259 for _, v := range tsr.Auths { 260 auths = append(auths, convertTimestampToV1(v)) 261 } 262 for _, v := range tsr.Votes { 263 votes = append(votes, convertTimestampToV1(v)) 264 } 265 266 return &v1.TimestampsReply{ 267 Auths: auths, 268 Details: details, 269 Votes: votes, 270 }, nil 271 } 272 273 func convertVoteStatusToPlugin(s v1.VoteStatusT) ticketvote.VoteStatusT { 274 switch s { 275 case v1.VoteStatusUnauthorized: 276 return ticketvote.VoteStatusUnauthorized 277 case v1.VoteStatusAuthorized: 278 return ticketvote.VoteStatusAuthorized 279 case v1.VoteStatusStarted: 280 return ticketvote.VoteStatusStarted 281 case v1.VoteStatusFinished: 282 return ticketvote.VoteStatusFinished 283 case v1.VoteStatusApproved: 284 return ticketvote.VoteStatusApproved 285 case v1.VoteStatusRejected: 286 return ticketvote.VoteStatusRejected 287 case v1.VoteStatusIneligible: 288 return ticketvote.VoteStatusIneligible 289 default: 290 return ticketvote.VoteStatusInvalid 291 } 292 } 293 294 func convertVoteTypeToPlugin(t v1.VoteT) ticketvote.VoteT { 295 switch t { 296 case v1.VoteTypeStandard: 297 return ticketvote.VoteTypeStandard 298 case v1.VoteTypeRunoff: 299 return ticketvote.VoteTypeRunoff 300 } 301 return ticketvote.VoteTypeInvalid 302 } 303 304 func convertVoteParamsToPlugin(v v1.VoteParams) ticketvote.VoteParams { 305 tv := ticketvote.VoteParams{ 306 Token: v.Token, 307 Version: v.Version, 308 Type: convertVoteTypeToPlugin(v.Type), 309 Mask: v.Mask, 310 Duration: v.Duration, 311 QuorumPercentage: v.QuorumPercentage, 312 PassPercentage: v.PassPercentage, 313 Parent: v.Parent, 314 } 315 // Convert vote options 316 vo := make([]ticketvote.VoteOption, 0, len(v.Options)) 317 for _, vi := range v.Options { 318 vo = append(vo, ticketvote.VoteOption{ 319 ID: vi.ID, 320 Description: vi.Description, 321 Bit: vi.Bit, 322 }) 323 } 324 tv.Options = vo 325 326 return tv 327 } 328 329 func convertStartDetailsToPlugin(sd v1.StartDetails) ticketvote.StartDetails { 330 return ticketvote.StartDetails{ 331 Params: convertVoteParamsToPlugin(sd.Params), 332 PublicKey: sd.PublicKey, 333 Signature: sd.Signature, 334 } 335 } 336 337 func convertStartToPlugin(vs v1.Start) ticketvote.Start { 338 starts := make([]ticketvote.StartDetails, 0, len(vs.Starts)) 339 for _, v := range vs.Starts { 340 starts = append(starts, convertStartDetailsToPlugin(v)) 341 } 342 return ticketvote.Start{ 343 Starts: starts, 344 } 345 } 346 347 func convertCastVotesToPlugin(votes []v1.CastVote) []ticketvote.CastVote { 348 cv := make([]ticketvote.CastVote, 0, len(votes)) 349 for _, v := range votes { 350 cv = append(cv, ticketvote.CastVote{ 351 Token: v.Token, 352 Ticket: v.Ticket, 353 VoteBit: v.VoteBit, 354 Signature: v.Signature, 355 }) 356 } 357 return cv 358 } 359 360 func convertVoteTypeToV1(t ticketvote.VoteT) v1.VoteT { 361 switch t { 362 case ticketvote.VoteTypeStandard: 363 return v1.VoteTypeStandard 364 case ticketvote.VoteTypeRunoff: 365 return v1.VoteTypeRunoff 366 } 367 return v1.VoteTypeInvalid 368 369 } 370 371 func convertVoteParamsToV1(v ticketvote.VoteParams) v1.VoteParams { 372 vp := v1.VoteParams{ 373 Token: v.Token, 374 Version: v.Version, 375 Type: convertVoteTypeToV1(v.Type), 376 Mask: v.Mask, 377 Duration: v.Duration, 378 QuorumPercentage: v.QuorumPercentage, 379 PassPercentage: v.PassPercentage, 380 } 381 vo := make([]v1.VoteOption, 0, len(v.Options)) 382 for _, o := range v.Options { 383 vo = append(vo, v1.VoteOption{ 384 ID: o.ID, 385 Description: o.Description, 386 Bit: o.Bit, 387 }) 388 } 389 vp.Options = vo 390 391 return vp 392 } 393 394 func convertVoteErrorToV1(e *ticketvote.VoteErrorT) *v1.VoteErrorT { 395 if e == nil { 396 return nil 397 } 398 399 var ve v1.VoteErrorT 400 switch *e { 401 case ticketvote.VoteErrorInvalid: 402 ve = v1.VoteErrorInvalid 403 case ticketvote.VoteErrorInternalError: 404 ve = v1.VoteErrorInternalError 405 case ticketvote.VoteErrorRecordNotFound: 406 ve = v1.VoteErrorRecordNotFound 407 case ticketvote.VoteErrorVoteBitInvalid: 408 ve = v1.VoteErrorVoteBitInvalid 409 case ticketvote.VoteErrorVoteStatusInvalid: 410 ve = v1.VoteErrorVoteStatusInvalid 411 case ticketvote.VoteErrorTicketAlreadyVoted: 412 ve = v1.VoteErrorTicketAlreadyVoted 413 case ticketvote.VoteErrorTicketNotEligible: 414 ve = v1.VoteErrorTicketNotEligible 415 default: 416 ve = v1.VoteErrorInternalError 417 } 418 419 return &ve 420 } 421 422 func convertCastVoteRepliesToV1(replies []ticketvote.CastVoteReply) []v1.CastVoteReply { 423 r := make([]v1.CastVoteReply, 0, len(replies)) 424 for _, v := range replies { 425 r = append(r, v1.CastVoteReply{ 426 Ticket: v.Ticket, 427 Receipt: v.Receipt, 428 ErrorCode: convertVoteErrorToV1(v.ErrorCode), 429 ErrorContext: v.ErrorContext, 430 }) 431 } 432 return r 433 } 434 435 func convertVoteDetailsToV1(vd ticketvote.VoteDetails) v1.VoteDetails { 436 return v1.VoteDetails{ 437 Params: convertVoteParamsToV1(vd.Params), 438 PublicKey: vd.PublicKey, 439 Signature: vd.Signature, 440 Receipt: vd.Receipt, 441 StartBlockHeight: vd.StartBlockHeight, 442 StartBlockHash: vd.StartBlockHash, 443 EndBlockHeight: vd.EndBlockHeight, 444 EligibleTickets: vd.EligibleTickets, 445 } 446 } 447 448 func convertAuthDetailsToV1(auths []ticketvote.AuthDetails) []v1.AuthDetails { 449 a := make([]v1.AuthDetails, 0, len(auths)) 450 for _, v := range auths { 451 a = append(a, v1.AuthDetails{ 452 Token: v.Token, 453 Version: v.Version, 454 Action: v.Action, 455 PublicKey: v.PublicKey, 456 Signature: v.Signature, 457 Timestamp: v.Timestamp, 458 Receipt: v.Receipt, 459 }) 460 } 461 return a 462 } 463 464 func convertCastVoteDetailsToV1(votes []ticketvote.CastVoteDetails) []v1.CastVoteDetails { 465 vs := make([]v1.CastVoteDetails, 0, len(votes)) 466 for _, v := range votes { 467 vs = append(vs, v1.CastVoteDetails{ 468 Token: v.Token, 469 Ticket: v.Ticket, 470 VoteBit: v.VoteBit, 471 Address: v.Address, 472 Signature: v.Signature, 473 Receipt: v.Receipt, 474 Timestamp: v.Timestamp, 475 }) 476 } 477 return vs 478 } 479 480 func convertVoteStatusToV1(s ticketvote.VoteStatusT) v1.VoteStatusT { 481 switch s { 482 case ticketvote.VoteStatusInvalid: 483 return v1.VoteStatusInvalid 484 case ticketvote.VoteStatusUnauthorized: 485 return v1.VoteStatusUnauthorized 486 case ticketvote.VoteStatusAuthorized: 487 return v1.VoteStatusAuthorized 488 case ticketvote.VoteStatusStarted: 489 return v1.VoteStatusStarted 490 case ticketvote.VoteStatusFinished: 491 return v1.VoteStatusFinished 492 case ticketvote.VoteStatusApproved: 493 return v1.VoteStatusApproved 494 case ticketvote.VoteStatusRejected: 495 return v1.VoteStatusRejected 496 case ticketvote.VoteStatusIneligible: 497 return v1.VoteStatusIneligible 498 default: 499 return v1.VoteStatusInvalid 500 } 501 } 502 503 func convertSummaryToV1(s ticketvote.SummaryReply) v1.Summary { 504 results := make([]v1.VoteResult, 0, len(s.Results)) 505 for _, v := range s.Results { 506 results = append(results, v1.VoteResult{ 507 ID: v.ID, 508 Description: v.Description, 509 VoteBit: v.VoteBit, 510 Votes: v.Votes, 511 }) 512 } 513 return v1.Summary{ 514 Type: convertVoteTypeToV1(s.Type), 515 Status: convertVoteStatusToV1(s.Status), 516 Duration: s.Duration, 517 StartBlockHeight: s.StartBlockHeight, 518 StartBlockHash: s.StartBlockHash, 519 EndBlockHeight: s.EndBlockHeight, 520 EligibleTickets: s.EligibleTickets, 521 QuorumPercentage: s.QuorumPercentage, 522 PassPercentage: s.PassPercentage, 523 Results: results, 524 BestBlock: s.BestBlock, 525 } 526 } 527 528 func convertSummariesToV1(s map[string]ticketvote.SummaryReply) map[string]v1.Summary { 529 ts := make(map[string]v1.Summary, len(s)) 530 for k, v := range s { 531 ts[k] = convertSummaryToV1(v) 532 } 533 return ts 534 } 535 536 func convertProofToV1(p ticketvote.Proof) v1.Proof { 537 return v1.Proof{ 538 Type: p.Type, 539 Digest: p.Digest, 540 MerkleRoot: p.MerkleRoot, 541 MerklePath: p.MerklePath, 542 ExtraData: p.ExtraData, 543 } 544 } 545 546 func convertTimestampToV1(t ticketvote.Timestamp) v1.Timestamp { 547 proofs := make([]v1.Proof, 0, len(t.Proofs)) 548 for _, v := range t.Proofs { 549 proofs = append(proofs, convertProofToV1(v)) 550 } 551 return v1.Timestamp{ 552 Data: t.Data, 553 Digest: t.Digest, 554 TxID: t.TxID, 555 MerkleRoot: t.MerkleRoot, 556 Proofs: proofs, 557 } 558 }