github.com/decred/politeia@v1.4.0/politeiad/backend/gitbe/cmsplugin/cmsplugin.go (about) 1 package cmsplugin 2 3 import ( 4 "encoding/hex" 5 "encoding/json" 6 "fmt" 7 8 "github.com/decred/politeia/politeiad/api/v1/identity" 9 "github.com/decred/politeia/util" 10 ) 11 12 type ErrorStatusT int 13 14 // Plugin settings, kinda doesn't go here but for now it is fine 15 const ( 16 Version = "1" 17 ID = "cms" 18 CmdVoteDetails = "votedccdetails" 19 CmdStartVote = "startdccvote" 20 CmdCastVote = "castdccvote" 21 CmdInventory = "cmsinventory" 22 CmdVoteSummary = "votedccsummary" 23 CmdDCCVoteResults = "dccvoteresults" 24 MDStreamVoteBits = 16 // Vote bits and mask 25 MDStreamVoteSnapshot = 17 // Vote tickets and start/end parameters 26 27 VoteDurationMin = 2016 // Minimum vote duration (in blocks) 28 VoteDurationMax = 4032 // Maximum vote duration (in blocks) 29 30 // Error status codes 31 ErrorStatusInvalid ErrorStatusT = 0 32 ErrorStatusInternalError ErrorStatusT = 1 33 ErrorStatusDCCNotFound ErrorStatusT = 2 34 ErrorStatusInvalidVoteBit ErrorStatusT = 3 35 ErrorStatusVoteHasEnded ErrorStatusT = 4 36 ErrorStatusDuplicateVote ErrorStatusT = 5 37 ErrorStatusIneligibleUserID ErrorStatusT = 6 38 ErrorStatusLast ErrorStatusT = 7 39 40 // String constant to ensure that the observed dcc vote option is tabulated 41 // as "approved" or "disapproved". 42 DCCApprovalString = "yes" 43 DCCDisapprovalString = "no" 44 ) 45 46 var ( 47 // ErrorStatus converts error status codes to human readable text. 48 ErrorStatus = map[ErrorStatusT]string{ 49 ErrorStatusInvalid: "invalid error status", 50 ErrorStatusInternalError: "internal error", 51 ErrorStatusDCCNotFound: "dcc not found", 52 ErrorStatusInvalidVoteBit: "invalid vote bit", 53 ErrorStatusVoteHasEnded: "vote has ended", 54 ErrorStatusDuplicateVote: "duplicate vote", 55 ErrorStatusIneligibleUserID: "inegligible user id", 56 } 57 ) 58 59 // VoteOption describes a single vote option. 60 type VoteOption struct { 61 Id string `json:"id"` // Single unique word identifying vote (e.g. yes) 62 Description string `json:"description"` // Longer description of the vote. 63 Bits uint64 `json:"bits"` // Bits used for this option 64 } 65 66 // Vote represents the vote options for vote that is identified by its token. 67 type Vote struct { 68 Token string `json:"token"` // Token that identifies vote 69 Mask uint64 `json:"mask"` // Valid votebits 70 Duration uint32 `json:"duration"` // Duration in blocks 71 QuorumPercentage uint32 `json:"quorumpercentage"` // Percent of eligible votes required for quorum 72 PassPercentage uint32 `json:"passpercentage"` // Percent of total votes required to pass 73 Options []VoteOption `json:"options"` // Vote option 74 } 75 76 // EncodeVote encodes Vote into a JSON byte slice. 77 func EncodeVote(v Vote) ([]byte, error) { 78 return json.Marshal(v) 79 } 80 81 // DecodeVote decodes a JSON byte slice into a Vote. 82 func DecodeVote(payload []byte) (*Vote, error) { 83 var v Vote 84 85 err := json.Unmarshal(payload, &v) 86 if err != nil { 87 return nil, err 88 } 89 90 return &v, nil 91 } 92 93 // CastVote is a signed vote. 94 type CastVote struct { 95 Token string `json:"token"` // DCC ID 96 UserID string `json:"publickey"` // User ID provided by cmswww 97 VoteBit string `json:"votebit"` // Vote bit that was selected, this is encode in hex 98 Signature string `json:"signature"` // Signature of the Token+VoteBit+UserID by the submitting user. 99 } 100 101 // EncodeCastVote encodes CastVotes into a JSON byte slice. 102 func EncodeCastVote(cv CastVote) ([]byte, error) { 103 return json.Marshal(cv) 104 } 105 106 // DecodeCastVote decodes a JSON byte slice into a CastVote. 107 func DecodeCastVote(payload []byte) (*CastVote, error) { 108 var cv CastVote 109 110 err := json.Unmarshal(payload, &cv) 111 if err != nil { 112 return nil, err 113 } 114 115 return &cv, nil 116 } 117 118 // CastVoteReply contains the signature or error to a cast vote command. The 119 // Error and ErrorStatus fields will only be populated if something went wrong 120 // while attempting to cast the vote. 121 type CastVoteReply struct { 122 ClientSignature string `json:"clientsignature"` // Signature that was sent in 123 Signature string `json:"signature"` // Signature of the ClientSignature 124 Error string `json:"error"` // Error status message 125 ErrorStatus ErrorStatusT `json:"errorstatus,omitempty"` // Error status code 126 } 127 128 // EncodeCastVoteReply encodes CastVoteReply into a JSON byte slice. 129 func EncodeCastVoteReply(cvr CastVoteReply) ([]byte, error) { 130 return json.Marshal(cvr) 131 } 132 133 // DecodeCastVoteReply decodes a JSON byte slice into a CastVote. 134 func DecodeCastVoteReply(payload []byte) (*CastVoteReply, error) { 135 var cvr CastVoteReply 136 137 err := json.Unmarshal(payload, &cvr) 138 if err != nil { 139 return nil, err 140 } 141 142 return &cvr, nil 143 } 144 145 // UserWeight describes a single vote option. 146 type UserWeight struct { 147 UserID string `json:"userid"` // Unique user id from cmswww. 148 Weight int64 `json:"weight"` // Calculated user voted weight, provided by cmswww. 149 } 150 151 const VersionStartVote = 1 152 153 // StartVote instructs the plugin to commence voting on a proposal with the 154 // provided vote bits. 155 type StartVote struct { 156 // decred plugin only data 157 Version uint `json:"version"` // Version of this structure 158 Token string `json:"token"` // Token 159 160 PublicKey string `json:"publickey"` // Key used for signature. 161 UserWeights []UserWeight `json:"userweights"` // Array of User ID + weight 162 Vote Vote `json:"vote"` // Vote + options 163 Signature string `json:"signature"` // Signature of Votehash 164 } 165 166 // EncodeStartVote a JSON byte slice. 167 func EncodeStartVote(v StartVote) ([]byte, error) { 168 return json.Marshal(v) 169 } 170 171 // DecodeStartVote a JSON byte slice into a StartVote. 172 func DecodeStartVote(payload []byte) (StartVote, error) { 173 var sv StartVote 174 175 err := json.Unmarshal(payload, &sv) 176 if err != nil { 177 return sv, err 178 } 179 180 return sv, nil 181 } 182 183 const VersionStartVoteReply = 1 184 185 // StartVoteReply is the reply to StartVote. 186 type StartVoteReply struct { 187 // cms plugin only data 188 Version uint `json:"version"` // Version of this structure 189 190 // Shared data 191 StartBlockHeight uint32 `json:"startblockheight"` // Block height 192 StartBlockHash string `json:"startblockhash"` // Block hash 193 EndHeight uint32 `json:"endheight"` // Height of vote end 194 } 195 196 // EncodeStartVoteReply encodes StartVoteReply into a JSON byte slice. 197 func EncodeStartVoteReply(v StartVoteReply) ([]byte, error) { 198 return json.Marshal(v) 199 } 200 201 // DecodeStartVoteReply decodes a JSON byte slice into a StartVoteReply. 202 func DecodeStartVoteReply(payload []byte) (StartVoteReply, error) { 203 var v StartVoteReply 204 205 err := json.Unmarshal(payload, &v) 206 if err != nil { 207 return v, err 208 } 209 210 return v, nil 211 } 212 213 // VoteDetails is used to retrieve the voting period details for a record. 214 type VoteDetails struct { 215 Token string `json:"token"` // Censorship token 216 } 217 218 // EncodeVoteDetails encodes VoteDetails into a JSON byte slice. 219 func EncodeVoteDetails(vd VoteDetails) ([]byte, error) { 220 return json.Marshal(vd) 221 } 222 223 // DecodeVoteDetails decodes a JSON byte slice into a VoteDetails. 224 func DecodeVoteDetails(payload []byte) (*VoteDetails, error) { 225 var vd VoteDetails 226 227 err := json.Unmarshal(payload, &vd) 228 if err != nil { 229 return nil, err 230 } 231 232 return &vd, nil 233 } 234 235 // VoteDetailsReply is the reply to VoteDetails. 236 type VoteDetailsReply struct { 237 StartVote StartVote `json:"startvote"` // Vote ballot 238 StartVoteReply StartVoteReply `json:"startvotereply"` // Start vote snapshot 239 } 240 241 // EncodeVoteDetailsReply encodes VoteDetailsReply into a JSON byte slice. 242 func EncodeVoteDetailsReply(vdr VoteDetailsReply) ([]byte, error) { 243 return json.Marshal(vdr) 244 } 245 246 // DecodeVoteReply decodes a JSON byte slice into a VoteDetailsReply. 247 func DecodeVoteDetailsReply(payload []byte) (*VoteDetailsReply, error) { 248 var vdr VoteDetailsReply 249 250 err := json.Unmarshal(payload, &vdr) 251 if err != nil { 252 return nil, err 253 } 254 255 return &vdr, nil 256 } 257 258 type VoteResults struct { 259 Token string `json:"token"` // Censorship token 260 } 261 262 type VoteResultsReply struct { 263 StartVote StartVote `json:"startvote"` // Original ballot 264 CastVotes []CastVote `json:"castvotes"` // All votes 265 } 266 267 // EncodeVoteResults encodes VoteResults into a JSON byte slice. 268 func EncodeVoteResults(v VoteResults) ([]byte, error) { 269 return json.Marshal(v) 270 } 271 272 // DecodeVoteResults decodes a JSON byte slice into a VoteResults. 273 func DecodeVoteResults(payload []byte) (*VoteResults, error) { 274 var v VoteResults 275 276 err := json.Unmarshal(payload, &v) 277 if err != nil { 278 return nil, err 279 } 280 281 return &v, nil 282 } 283 284 // EncodeVoteResultsReply encodes VoteResults into a JSON byte slice. 285 func EncodeVoteResultsReply(v VoteResultsReply) ([]byte, error) { 286 return json.Marshal(v) 287 } 288 289 // DecodeVoteResultsReply decodes a JSON byte slice into a VoteResults. 290 func DecodeVoteResultsReply(payload []byte) (*VoteResultsReply, error) { 291 var v VoteResultsReply 292 293 err := json.Unmarshal(payload, &v) 294 if err != nil { 295 return nil, err 296 } 297 298 return &v, nil 299 } 300 301 // VoteSummary requests a summary of a proposal vote. This includes certain 302 // voting period parameters and a summary of the vote results. 303 type VoteSummary struct { 304 Token string `json:"token"` // Censorship token 305 } 306 307 // EncodeVoteSummary encodes VoteSummary into a JSON byte slice. 308 func EncodeVoteSummary(v VoteSummary) ([]byte, error) { 309 return json.Marshal(v) 310 } 311 312 // DecodeVoteSummary decodes a JSON byte slice into a VoteSummary. 313 func DecodeVoteSummary(payload []byte) (*VoteSummary, error) { 314 var v VoteSummary 315 316 err := json.Unmarshal(payload, &v) 317 if err != nil { 318 return nil, err 319 } 320 321 return &v, nil 322 } 323 324 // VoteOptionResult describes a vote option and the total number of votes that 325 // have been cast for this option. 326 type VoteOptionResult struct { 327 ID string `json:"id"` // Single unique word identifying vote (e.g. yes) 328 Description string `json:"description"` // Longer description of the vote. 329 Bits uint64 `json:"bits"` // Bits used for this option 330 Votes uint64 `json:"votes"` // Number of votes cast for this option 331 } 332 333 // VoteSummaryReply is the reply to the VoteSummary command and returns certain 334 // voting period parameters as well as a summary of the vote results. 335 type VoteSummaryReply struct { 336 Duration uint32 `json:"duration"` // Vote duration 337 EndHeight uint32 `json:"endheight"` // End block height 338 PassPercentage uint32 `json:"passpercentage"` // Percent of total votes required to pass 339 Results []VoteOptionResult `json:"results"` // Vote results 340 } 341 342 // EncodeVoteSummaryReply encodes VoteSummary into a JSON byte slice. 343 func EncodeVoteSummaryReply(v VoteSummaryReply) ([]byte, error) { 344 return json.Marshal(v) 345 } 346 347 // DecodeVoteSummaryReply decodes a JSON byte slice into a VoteSummaryReply. 348 func DecodeVoteSummaryReply(payload []byte) (*VoteSummaryReply, error) { 349 var v VoteSummaryReply 350 351 err := json.Unmarshal(payload, &v) 352 if err != nil { 353 return nil, err 354 } 355 356 return &v, nil 357 } 358 359 // Inventory is used to retrieve the decred plugin inventory. 360 type Inventory struct{} 361 362 // EncodeInventory encodes Inventory into a JSON byte slice. 363 func EncodeInventory(i Inventory) ([]byte, error) { 364 return json.Marshal(i) 365 } 366 367 // DecodeInventory decodes a JSON byte slice into a Inventory. 368 func DecodeInventory(payload []byte) (*Inventory, error) { 369 var i Inventory 370 371 err := json.Unmarshal(payload, &i) 372 if err != nil { 373 return nil, err 374 } 375 376 return &i, nil 377 } 378 379 // StartVoteTuple is used to return the StartVote and StartVoteReply for a 380 // record. StartVoteReply does not contain any record identifying data so it 381 // must be returned with the StartVote in order to know what record it belongs 382 // to. 383 type StartVoteTuple struct { 384 StartVote StartVote `json:"startvote"` // Start vote 385 StartVoteReply StartVoteReply `json:"startvotereply"` // Start vote reply 386 } 387 388 // InventoryReply returns the cms plugin inventory. 389 type InventoryReply struct { 390 StartVoteTuples []StartVoteTuple `json:"startvotetuples"` // Start vote tuples 391 CastVotes []CastVote `json:"castvotes"` // Cast votes 392 } 393 394 // EncodeInventoryReply encodes a InventoryReply into a JSON byte slice. 395 func EncodeInventoryReply(ir InventoryReply) ([]byte, error) { 396 return json.Marshal(ir) 397 } 398 399 // DecodeInventoryReply decodes a JSON byte slice into a inventory. 400 func DecodeInventoryReply(payload []byte) (*InventoryReply, error) { 401 var ir InventoryReply 402 403 err := json.Unmarshal(payload, &ir) 404 if err != nil { 405 return nil, err 406 } 407 408 return &ir, nil 409 } 410 411 // LoadVoteResults creates a vote results entry in the cache for any proposals 412 // that have finsished voting but have not yet been added to the lazy loaded 413 // vote results table. 414 type LoadVoteResults struct { 415 BestBlock uint64 `json:"bestblock"` // Best block height 416 } 417 418 // EncodeLoadVoteResults encodes a LoadVoteResults into a JSON byte slice. 419 func EncodeLoadVoteResults(lvr LoadVoteResults) ([]byte, error) { 420 return json.Marshal(lvr) 421 } 422 423 // DecodeLoadVoteResults decodes a JSON byte slice into a LoadVoteResults. 424 func DecodeLoadVoteResults(payload []byte) (*LoadVoteResults, error) { 425 var lvr LoadVoteResults 426 427 err := json.Unmarshal(payload, &lvr) 428 if err != nil { 429 return nil, err 430 } 431 432 return &lvr, nil 433 } 434 435 // LoadVoteResultsReply is the reply to the LoadVoteResults command. 436 type LoadVoteResultsReply struct{} 437 438 // EncodeLoadVoteResultsReply encodes a LoadVoteResultsReply into a JSON 439 // byte slice. 440 func EncodeLoadVoteResultsReply(reply LoadVoteResultsReply) ([]byte, error) { 441 return json.Marshal(reply) 442 } 443 444 // DecodeLoadVoteResultsReply decodes a JSON byte slice into a LoadVoteResults. 445 func DecodeLoadVoteResultsReply(payload []byte) (*LoadVoteResultsReply, error) { 446 var reply LoadVoteResultsReply 447 448 err := json.Unmarshal(payload, &reply) 449 if err != nil { 450 return nil, err 451 } 452 453 return &reply, nil 454 } 455 456 // VerifySignature verifies that the StartVoteV2 signature is correct. 457 func (s *StartVote) VerifySignature() error { 458 sig, err := util.ConvertSignature(s.Signature) 459 if err != nil { 460 return err 461 } 462 b, err := hex.DecodeString(s.PublicKey) 463 if err != nil { 464 return err 465 } 466 pk, err := identity.PublicIdentityFromBytes(b) 467 if err != nil { 468 return err 469 } 470 vb, err := json.Marshal(s.Vote) 471 if err != nil { 472 return err 473 } 474 msg := hex.EncodeToString(util.Digest(vb)) 475 if !pk.VerifyMessage([]byte(msg), sig) { 476 return fmt.Errorf("invalid signature") 477 } 478 return nil 479 }