github.com/decred/politeia@v1.4.0/politeiad/plugins/pi/pi.go (about) 1 // Copyright (c) 2020-2022 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 pi provides a plugin that extends records with functionality for 6 // decred's proposal system. 7 package pi 8 9 const ( 10 // PluginID is the unique identifier for this plugin. 11 PluginID = "pi" 12 13 // CmdSetBillingStatus command sets the billing status. 14 CmdSetBillingStatus = "setbillingstatus" 15 16 // CmdBillingStatusChanges command returns the billing status changes 17 // of a proposal. 18 CmdBillingStatusChanges = "billingstatuschanges" 19 20 // CmdSummary command returns a summary for a proposal. 21 CmdSummary = "summary" 22 ) 23 24 // Plugin setting keys can be used to specify custom plugin settings. Default 25 // plugin setting values can be overridden by providing a plugin setting key 26 // and value to the plugin on startup. 27 const ( 28 // SettingKeyTextFileSizeMax is the plugin setting key for the 29 // SettingTextFileSizeMax plugin setting. 30 SettingKeyTextFileSizeMax = "textfilesizemax" 31 32 // SettingKeyImageFileCountMax is the plugin setting key for the 33 // SettingImageFileCountMax plugin setting. 34 SettingKeyImageFileCountMax = "imagefilecountmax" 35 36 // SettingKeyImageFileSizeMax is the plugin setting key for the 37 // SettingImageFileSizeMax plugin setting. 38 SettingKeyImageFileSizeMax = "imagefilesizemax" 39 40 // SettingKeyTitleLengthMin is the plugin setting key for 41 // the SettingTitleLengthMin plugin setting. 42 SettingKeyTitleLengthMin = "titlelengthmin" 43 44 // SettingKeyTitleLengthMax is the plugin setting key for 45 // the SettingTitleLengthMax plugin setting. 46 SettingKeyTitleLengthMax = "titlelengthmax" 47 48 // SettingKeyTitleSupportedChars is the plugin setting key 49 // for the SettingTitleSupportedChars plugin setting. 50 SettingKeyTitleSupportedChars = "titlesupportedchars" 51 52 // SettingKeyProposalAmountMin is the plugin setting key for 53 // the SettingProposalAmountMin plugin setting. 54 SettingKeyProposalAmountMin = "proposalamountmin" 55 56 // SettingKeyProposalAmountMax is the plugin setting key for 57 // the SettingProposalAmountMax plugin setting. 58 SettingKeyProposalAmountMax = "proposalamountmax" 59 60 // SettingKeyProposalStartDateMin is the plugin setting key for 61 // the SettingProposalStartDateMin plugin setting. 62 SettingKeyProposalStartDateMin = "proposalstartdatemin" 63 64 // SettingKeyProposalEndDateMax is the plugin setting key for 65 // the SettingProposalEndDateMax plugin setting. 66 SettingKeyProposalEndDateMax = "proposalenddatemax" 67 68 // SettingKeyProposalDomains is the plugin setting key for the 69 // SettingProposalDomains plugin setting. 70 SettingKeyProposalDomains = "proposaldomains" 71 72 // SettingKeyBillingStatusChangesMax is the plugin setting 73 // key for the SettingBillingStatusChangesMax plugin setting. 74 SettingKeyBillingStatusChangesMax = "billingstatuschangesmax" 75 76 // SettingKeySummariesPageSize is the plugin setting key for the 77 // SettingSummariesPageSize plugin setting. 78 SettingKeySummariesPageSize = "summariespagesize" 79 80 // SettingKeyBillingStatusChangesPageSize is the plugin key for 81 // the SettingBillingStatusChangesPageSize plugin setting. 82 SettingKeyBillingStatusChangesPageSize = "billingstatuschangespagesize" 83 ) 84 85 // Plugin setting default values. These can be overridden by providing a plugin 86 // setting key and value to the plugin on startup. 87 const ( 88 // SettingTextFileSizeMax is the default maximum allowed size of a 89 // text file in bytes. 90 SettingTextFileSizeMax uint32 = 512 * 1024 91 92 // SettingImageFileCountMax is the default maximum number of image 93 // files that can be included in a proposal. 94 SettingImageFileCountMax uint32 = 5 95 96 // SettingImageFileSizeMax is the default maximum allowed size of 97 // an image file in bytes. 98 SettingImageFileSizeMax uint32 = 512 * 1024 99 100 // SettingTitleLengthMin is the default minimum number of 101 // characters that a proposal name or a proposal update title can be. 102 SettingTitleLengthMin uint32 = 8 103 104 // SettingTitleLengthMax is the default maximum number of 105 // characters that a proposal name or a proposal update title can be. 106 SettingTitleLengthMax uint32 = 80 107 108 // SettingProposalAmountMin is the default minimum funding amount 109 // in cents a proposal can have. 110 SettingProposalAmountMin uint64 = 100000 // 1k usd in cents. 111 112 // SettingProposalAmountMax is the default maximum funding amount 113 // in cents a proposal can have. 114 SettingProposalAmountMax uint64 = 100000000 // 1m usd in cents. 115 116 // SettingProposalEndDateMax is the default maximum possible proposal 117 // end date - seconds from current time. 118 SettingProposalEndDateMax int64 = 31557600 // 365.25 days in seconds. 119 120 // SettingProposalStartDateMin is the default minimum possible proposal 121 // start date - seconds from current time. 122 SettingProposalStartDateMin int64 = 604800 // One week in seconds. 123 124 // SettingBillingStatusChangesMax is the default maximum allowed 125 // billing status changes. 126 SettingBillingStatusChangesMax uint32 = 1 127 128 // SettingSummariesPageSize is the default maximum number of proposal 129 // summaries that can be requested at any one time. 130 SettingSummariesPageSize uint32 = 5 131 132 // SettingBillingStatusChangesPageSize is the default maximum number of 133 // billing status changes that can be requested at any one time. 134 SettingBillingStatusChangesPageSize uint32 = 5 135 ) 136 137 var ( 138 // SettingTitleSupportedChars contains the supported 139 // characters in a proposal name or a proposal update title. 140 SettingTitleSupportedChars = []string{ 141 "A-z", "0-9", "&", ".", ",", ":", ";", "-", " ", "@", "+", "#", 142 "/", "(", ")", "!", "?", "\"", "'", 143 } 144 145 // SettingProposalDomains contains the default proposal domains. 146 SettingProposalDomains = []string{ 147 "development", 148 "marketing", 149 "research", 150 "design", 151 } 152 ) 153 154 // ErrorCodeT represents a plugin error that was caused by the user. 155 type ErrorCodeT uint32 156 157 const ( 158 // ErrorCodeInvalid represents an invalid error code. 159 ErrorCodeInvalid ErrorCodeT = 0 160 161 // ErrorCodeTextFileNameInvalid is returned when a text file has 162 // a file name that is not allowed. 163 ErrorCodeTextFileNameInvalid ErrorCodeT = 1 164 165 // ErrorCodeTextFileSizeInvalid is returned when a text file size 166 // exceedes the TextFileSizeMax setting. 167 ErrorCodeTextFileSizeInvalid ErrorCodeT = 2 168 169 // ErrorCodeTextFileMissing is returned when the proposal does not 170 // contain one or more of the required text files. 171 ErrorCodeTextFileMissing ErrorCodeT = 3 172 173 // ErrorCodeImageFileCountInvalid is returned when the number of 174 // image attachments exceedes the ImageFileCountMax setting. 175 ErrorCodeImageFileCountInvalid ErrorCodeT = 4 176 177 // ErrorCodeImageFileSizeInvalid is returned when an image file 178 // size exceedes the ImageFileSizeMax setting. 179 ErrorCodeImageFileSizeInvalid ErrorCodeT = 5 180 181 // ErrorCodeTitleInvalid is returned when a title, proposal title or proposal 182 // update title, does not adhere to the title regexp requirements. 183 ErrorCodeTitleInvalid ErrorCodeT = 6 184 185 // ErrorCodeVoteStatusInvalid is returned when a proposal vote 186 // status does not allow changes to be made to the proposal. 187 ErrorCodeVoteStatusInvalid ErrorCodeT = 7 188 189 // ErrorCodeProposalStartDateInvalid is returned when a proposal start date 190 // does not adhere to the proposal start date settings. 191 ErrorCodeProposalStartDateInvalid ErrorCodeT = 8 192 193 // ErrorCodeProposalEndDateInvalid is returned when a proposal end date 194 // does not adhere to the proposal end date settings. 195 ErrorCodeProposalEndDateInvalid ErrorCodeT = 9 196 197 // ErrorCodeProposalAmountInvalid is returned when a proposal amount 198 // is not in the range defined by the amount min/max plugin settings. 199 ErrorCodeProposalAmountInvalid ErrorCodeT = 10 200 201 // ErrorCodeProposalDomainInvalid is returned when a proposal domain 202 // is not one of the supported domains. 203 ErrorCodeProposalDomainInvalid ErrorCodeT = 11 204 205 // ErrorCodeTokenInvalid is returned when a record token is 206 // provided as part of a plugin command payload and is not a valid 207 // token or the payload token does not match the token that was 208 // used in the API request. 209 ErrorCodeTokenInvalid ErrorCodeT = 12 210 211 // ErrorCodePublicKeyInvalid is returned when a public key is not 212 // a valid hex encoded, Ed25519 public key. 213 ErrorCodePublicKeyInvalid ErrorCodeT = 13 214 215 // ErrorCodeSignatureInvalid is returned when a signature is not 216 // a valid hex encoded, Ed25519 signature or when the signature is 217 // wrong. 218 ErrorCodeSignatureInvalid ErrorCodeT = 14 219 220 // ErrorCodeBillingStatusChangeNotAllowed is returned when a billing status 221 // change is not allowed. 222 ErrorCodeBillingStatusChangeNotAllowed = 15 223 224 // ErrorCodeBillingStatusInvalid is returned when an invalid billing status 225 // is provided. 226 ErrorCodeBillingStatusInvalid = 16 227 228 // ErrorCodeCommentWriteNotAllowed is returned when a user attempts to submit 229 // a new comment or a comment vote, but does not have permission to. This 230 // could be because the proposal's vote status does not allow for any 231 // additional changes or because the user is trying to write to a thread that 232 // is not allowed. Example, once a proposal vote is approved the only comment 233 // writes that are allowed are replies and votes to the author's most recent 234 // update thread. 235 ErrorCodeCommentWriteNotAllowed = 17 236 237 // ErrorCodeExtraDataHintInvalid is returned when the extra data hint is 238 // invalid. 239 ErrorCodeExtraDataHintInvalid = 18 240 241 // ErrorCodeExtraDataInvalid is returned when the extra data payload is 242 // invalid. 243 ErrorCodeExtraDataInvalid = 19 244 245 // ErrorCodeLegacyTokenNotAllowed is returned when the legacy token is set 246 // during a normal proposal submission. 247 ErrorCodeLegacyTokenNotAllowed = 20 248 249 // ErrorCodeLast is used by unit tests to verify that all error codes have 250 // a human readable entry in the ErrorCodes map. This error will never be 251 // returned. 252 ErrorCodeLast ErrorCodeT = 21 253 ) 254 255 var ( 256 // ErrorCodes contains the human readable errors. 257 ErrorCodes = map[ErrorCodeT]string{ 258 ErrorCodeInvalid: "error code invalid", 259 ErrorCodeTextFileNameInvalid: "text file name invalid", 260 ErrorCodeTextFileSizeInvalid: "text file size invalid", 261 ErrorCodeTextFileMissing: "text file is misisng", 262 ErrorCodeImageFileCountInvalid: "image file count invalid", 263 ErrorCodeImageFileSizeInvalid: "image file size invalid", 264 ErrorCodeTitleInvalid: "title invalid", 265 ErrorCodeVoteStatusInvalid: "vote status invalid", 266 ErrorCodeProposalAmountInvalid: "proposal amount invalid", 267 ErrorCodeProposalStartDateInvalid: "proposal start date invalid", 268 ErrorCodeProposalEndDateInvalid: "proposal end date invalid", 269 ErrorCodeProposalDomainInvalid: "proposal domain invalid", 270 ErrorCodeTokenInvalid: "token invalid", 271 ErrorCodePublicKeyInvalid: "public key invalid", 272 ErrorCodeSignatureInvalid: "signature invalid", 273 ErrorCodeBillingStatusChangeNotAllowed: "billing status change is not allowed", 274 ErrorCodeBillingStatusInvalid: "billing status invalid", 275 ErrorCodeCommentWriteNotAllowed: "comment write not allowed", 276 ErrorCodeExtraDataHintInvalid: "extra data hint invalid", 277 ErrorCodeLegacyTokenNotAllowed: "setting legacy token is not allowed", 278 ErrorCodeExtraDataInvalid: "extra data payload invalid", 279 } 280 ) 281 282 const ( 283 // FileNameIndexFile is the file name of the proposal markdown 284 // file. Every proposal is required to have an index file. The 285 // index file should contain the proposal content. 286 FileNameIndexFile = "index.md" 287 288 // FileNameProposalMetadata is the filename of the ProposalMetadata 289 // file that is saved to politeiad. ProposalMetadata is saved to 290 // politeiad as a file, not as a metadata stream, since it contains 291 // user provided metadata and needs to be included in the merkle 292 // root that politeiad signs. 293 FileNameProposalMetadata = "proposalmetadata.json" 294 ) 295 296 // ProposalMetadata contains metadata that is provided by the user as part of 297 // the proposal submission bundle. The proposal metadata is included in the 298 // proposal signature since it is user specified data. The ProposalMetadata 299 // object is saved to politeiad as a file, not as a metadata stream, since it 300 // needs to be included in the merkle root that politeiad signs. 301 type ProposalMetadata struct { 302 Name string `json:"name"` 303 Amount uint64 `json:"amount"` // Funding amount in cents 304 StartDate int64 `json:"startdate"` // Start date, Unix time 305 EndDate int64 `json:"enddate"` // Estimated end date, Unix time 306 Domain string `json:"domain"` // Proposal domain 307 308 // LegacyToken will only be set for legacy proposals that have been imported 309 // from the deprecated git backend into the tstore backend. The LegacyToken 310 // corresponds to the original token that was assigned to the proposal during 311 // submission to the git backed. This token is not used for anything in the 312 // current tstore backend, but can be used to lookup the proposal's original 313 // timestamps in the legacy proposal git repo. The proposal is assigned a 314 // new token by the tstore backend on import. An error is returned if this 315 // field is attempted to be set during normal proposal submissions. 316 LegacyToken string `json:"legacytoken,omitempty"` 317 } 318 319 // BillingStatusT represents the billing status of a proposal that has been 320 // approved by the Decred stakeholders. 321 type BillingStatusT uint32 322 323 const ( 324 // BillingStatusInvalid is an invalid billing status. 325 BillingStatusInvalid BillingStatusT = 0 326 327 // BillingStatusActive represents a proposal that was approved by 328 // the Decred stakeholders and is being actively billed against. 329 BillingStatusActive BillingStatusT = 1 330 331 // BillingStatusClosed represents a proposal that was approved by 332 // the Decred stakeholders, but has been closed by an admin prior 333 // to the proposal being completed. The most common reason for this 334 // is because a proposal author failed to deliver on the work that 335 // was funded in the proposal. A closed proposal can no longer be 336 // billed against. 337 BillingStatusClosed BillingStatusT = 2 338 339 // BillingStatusCompleted represents a proposal that was approved 340 // by the Decred stakeholders and has been successfully completed. 341 // A completed proposal can no longer be billed against. A proposal 342 // is marked as completed by an admin. 343 BillingStatusCompleted BillingStatusT = 3 344 345 // BillingStatusLast is used by unit tests to verify that all billing 346 // statuses have a human readable entry in the BillingStatuses map. This 347 // status will never be returned. 348 BillingStatusLast ErrorCodeT = 4 349 ) 350 351 var ( 352 // BillingStatuses contains the human readable billing statuses. 353 BillingStatuses = map[BillingStatusT]string{ 354 BillingStatusInvalid: "invalid", 355 BillingStatusActive: "active", 356 BillingStatusClosed: "closed", 357 BillingStatusCompleted: "completed", 358 } 359 ) 360 361 // BillingStatusChange represents the structure that is saved to disk when 362 // a proposal has its billing status updated. Some billing status changes 363 // require a reason to be given. Only admins can update the billing status 364 // of a proposal. 365 // 366 // PublicKey is the admin public key that can be used to verify the signature. 367 // 368 // Signature is the admin signature of the Token+Status+Reason. 369 // 370 // Receipt is the server signature of the admin signature. 371 // 372 // The PublicKey, Signature, and Receipt are all hex encoded and use the 373 // ed25519 signature scheme. 374 type BillingStatusChange struct { 375 Token string `json:"token"` 376 Status BillingStatusT `json:"status"` 377 Reason string `json:"reason,omitempty"` 378 PublicKey string `json:"publickey"` 379 Signature string `json:"signature"` 380 Receipt string `json:"receipt"` 381 Timestamp int64 `json:"timestamp"` // Unix timestamp 382 } 383 384 // SetBillingStatus sets the billing status of a proposal. Some billing status 385 // changes require a reason to be given. Only admins can update the billing 386 // status of a proposal. 387 // 388 // PublicKey is the admin public key that can be used to verify the signature. 389 // 390 // Signature is the admin signature of the Token+Status+Reason. 391 // 392 // The PublicKey and Signature are hex encoded and use the ed25519 signature 393 // scheme. 394 type SetBillingStatus struct { 395 Token string `json:"token"` 396 Status BillingStatusT `json:"status"` 397 Reason string `json:"reason,omitempty"` 398 PublicKey string `json:"publickey"` 399 Signature string `json:"signature"` 400 } 401 402 // SetBillingStatusReply is the reply to the SetBillingStatus command. 403 // 404 // Receipt is the server signature of the client signature. It is hex encoded 405 // and uses the ed25519 signature scheme. 406 type SetBillingStatusReply struct { 407 Receipt string `json:"receipt"` 408 Timestamp int64 `json:"timestamp"` // Unix timestamp 409 } 410 411 // Summary requests the summary of a proposal. 412 type Summary struct { 413 Token string `json:"token"` 414 } 415 416 // SummaryReply is the reply to the Summary command. 417 type SummaryReply struct { 418 Summary ProposalSummary `json:"summary"` 419 } 420 421 // ProposalSummary summarizes proposal information. 422 type ProposalSummary struct { 423 Status PropStatusT `json:"status"` 424 } 425 426 // PropStatusT represents the status of a proposal. It combines record and 427 // plugin metadata in order to create a unified map of the various paths a 428 // proposal can take throughout the proposal process. This serves as the 429 // source of truth for clients so that they don't need to try and decipher 430 // what various combinations of plugin metadata mean for the proposal. 431 // 432 // The proposal status is determined at runtime by the pi plugin based on the 433 // various record and plugin metadata that a proposal contains. 434 type PropStatusT string 435 436 const ( 437 // PropStatusInvalid represents an invalid proposal status. 438 PropStatusInvalid PropStatusT = "invalid" 439 440 // PropStatusUnvetted represents a proposal that has been submitted but has 441 // not yet been made public by the admins. 442 PropStatusUnvetted PropStatusT = "unvetted" 443 444 // PropStatusUnvettedAbandoned represents a proposal that has been 445 // submitted, but was abandoned by the author prior to being made public. 446 // The proposal can be marked as abandoned by either the proposal author 447 // or an admin. Abandoned proposal files are not deleted from the backend 448 // and are still retreivable. An abandoned proposal is locked against any 449 // additional proposal or plugin changes. 450 PropStatusUnvettedAbandoned PropStatusT = "unvetted-abandoned" 451 452 // PropStatusUnvettedCensored represents a proposal that has been submitted, 453 // but was censored by an admin prior to being made public. Censored 454 // proposal files are permanently deleted from the backend. No additional 455 // changes can be made to the proposal once it has been censored. 456 PropStatusUnvettedCensored PropStatusT = "unvetted-censored" 457 458 // PropStatusUnderReview represents a proposal that has been made public and 459 // is being reviewed by the Decred stakeholders, but has not had it's voting 460 // period started yet. 461 PropStatusUnderReview PropStatusT = "under-review" 462 463 // PropStatusAbandoned represents a proposal that has been made public, but 464 // has been abandoned. A proposal can be marked as abandoned by either the 465 // proposal author or by an admin. Abandoned proposals are locked from any 466 // additional changes. Abandoned proposal files are not deleted from the 467 // backend. 468 PropStatusAbandoned PropStatusT = "abandoned" 469 470 // PropStatusCensored represents a proposal that was censored by an admin 471 // after it had already been made public. This can happen if an edit to the 472 // proposal adds content that requires censoring. Censored proposal files 473 // are permanently deleted from the backend. No additional changes can be 474 // made to the proposal once it has been censored. 475 PropStatusCensored PropStatusT = "censored" 476 477 // PropStatusVoteAuthorized represents a public proposal whose voting period 478 // has been authorized by the author. An admin cannot start the voting 479 // period of a proposal until the author has authorized it. 480 PropStatusVoteAuthorized PropStatusT = "vote-authorized" 481 482 // PropStatusVoteStarted represents a public proposal that is currently 483 // under vote by the Decred stakeholders. The voting period for a proposal 484 // is started by an admin. The proposal content cannot change once the 485 // voting period has been started, but some plugin data (e.g. comments) can 486 // still be added. 487 PropStatusVoteStarted PropStatusT = "vote-started" 488 489 // PropStatusApproved represents a proposal that was voted on by the Decred 490 // stakeholders, met the approval criteria, but is not being actively billed 491 // against. An example is an RFP proposal. RFP proposals do not request 492 // funding and are not billed against once approved. 493 PropStatusApproved PropStatusT = "approved" 494 495 // PropStatusRejected represents a proposal that was voted on by the Decred 496 // stakeholders and did not meet the approval criteria. A rejected proposal 497 // is locked against any additional proposal or plugin changes. 498 PropStatusRejected PropStatusT = "rejected" 499 500 // PropStatusActive represents a proposal that was voted on by the Decred 501 // stakeholders, met the approval criteria, and is now eligible to be billed 502 // against. The proposal automatically becomes active once the voting period 503 // ends. The proposal content of an active proposal cannot be altered. Some 504 // plugin functionality is still allowed. For example, an author is allowed 505 // to start a new comment thread in order to give proposal updates that 506 // users can reply to. 507 PropStatusActive PropStatusT = "active" 508 509 // PropStatusCompleted represents a proposal that was funded by the Decred 510 // stakeholders and has been completed. A completed proposal is marked as 511 // completed by an admin and is no longer being billed against. A completed 512 // proposal is locked against any additional proposal or plugin changes. 513 PropStatusCompleted PropStatusT = "completed" 514 515 // PropStatusClosed represents a proposal that was funded, but was never 516 // completed. A proposal is marked as closed by an admin and cannot be 517 // billed against any further. The most common reason a proposal would be 518 // closed is because the author failed to deliver on the milestones laid 519 // out in the proposal. A closed proposal is locked against any additional 520 // proposal or plugin changes. 521 PropStatusClosed PropStatusT = "closed" 522 ) 523 524 const ( 525 // ProposalUpdateHint is the hint that is included in a comment's 526 // ExtraDataHint field to indicate that the comment is an update 527 // from the proposal author. 528 ProposalUpdateHint = "proposalupdate" 529 ) 530 531 // ProposalUpdateMetadata contains the metadata that is attached to a comment 532 // in the comment's ExtraData field to indicate that the comment is an update 533 // from the proposal author. 534 type ProposalUpdateMetadata struct { 535 Title string `json:"title"` 536 } 537 538 // BillingStatusChanges requests the billing status changes for the provided 539 // proposal token. 540 type BillingStatusChanges struct { 541 Token string `json:"token"` 542 } 543 544 // BillingStatusChangesReply is the reply to the BillingStatusChanges command. 545 type BillingStatusChangesReply struct { 546 BillingStatusChanges []BillingStatusChange `json:"billingstatuschanges"` 547 }