github.com/decred/politeia@v1.4.0/politeiad/api/v2/v2.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 v2 6 7 import "fmt" 8 9 const ( 10 // APIRoute is prefixed onto all routes in this package. 11 APIRoute = "/v2" 12 13 // RouteRecordNew creates a new record. 14 RouteRecordNew = "/recordnew" 15 16 // RouteRecordEdit edits a record. 17 RouteRecordEdit = "/recordedit" 18 19 // RouteRecordEditMetadata edits record's metadata. 20 RouteRecordEditMetadata = "/recordeditmetadata" 21 22 // RouteRecordSetStatus sets the status of a record. 23 RouteRecordSetStatus = "/recordsetstatus" 24 25 // RouteRecordTimestamps returns the record timestamps. 26 RouteRecordTimestamps = "/recordtimestamps" 27 28 // RouteRecords retrieves a page of records. 29 RouteRecords = "/records" 30 31 // RouteInventory returns the tokens of records in the inventory 32 // categorized by record state and record status. 33 RouteInventory = "/inventory" 34 35 // RouteInventoryOrdered returns a page of record tokens ordered by the 36 // timestamp of their most recent status change from newest to 37 // oldest. The returned tokens will include all record statuses. 38 RouteInventoryOrdered = "/inventoryordered" 39 40 // RoutePluginWrite executes a plugin command that writes data. 41 RoutePluginWrite = "/pluginwrite" 42 43 // RoutePluginReads executes a read-only plugin command. 44 RoutePluginReads = "/pluginreads" 45 46 // RoutePluginInventory returns all registered plugins. 47 RoutePluginInventory = "/plugininventory" 48 49 // ChallengeSize is the size of a request challenge token in bytes. 50 ChallengeSize = 32 51 ) 52 53 // ErrorCodeT represents a user error code. 54 type ErrorCodeT uint32 55 56 const ( 57 // ErrorCodeInvalid is an invalid error code. 58 ErrorCodeInvalid ErrorCodeT = 0 59 60 // ErrorCodeRequestPayloadInvalid is returned when a request's payload 61 // is invalid. 62 ErrorCodeRequestPayloadInvalid ErrorCodeT = 1 63 64 // ErrorCodeChallengeInvalid is returned when a challenge is invalid. 65 ErrorCodeChallengeInvalid ErrorCodeT = 2 66 67 // ErrorCodeMetadataStreamInvalid is returned when a metadata stream 68 // is invalid. 69 ErrorCodeMetadataStreamInvalid ErrorCodeT = 3 70 71 // ErrorCodeMetadataStreamDuplicate is returned when a metadata stream 72 // is a duplicate. 73 ErrorCodeMetadataStreamDuplicate ErrorCodeT = 4 74 75 // ErrorCodeFilesEmpty is returned when no files found. 76 ErrorCodeFilesEmpty ErrorCodeT = 5 77 78 // ErrorCodeFileNameInvalid is returned when a file name is invalid. 79 ErrorCodeFileNameInvalid ErrorCodeT = 6 80 81 // ErrorCodeFileNameDuplicate is returned when a file name is a duplicate. 82 ErrorCodeFileNameDuplicate ErrorCodeT = 7 83 84 // ErrorCodeFileDigestInvalid is returned when a file digest is invalid. 85 ErrorCodeFileDigestInvalid ErrorCodeT = 8 86 87 // ErrorCodeFilePayloadInvalid is returned when a file payload is invalid. 88 ErrorCodeFilePayloadInvalid ErrorCodeT = 9 89 90 // ErrorCodeFileMIMETypeInvalid is returned when a file MIME type is 91 // invalid. 92 ErrorCodeFileMIMETypeInvalid ErrorCodeT = 10 93 94 // ErrorCodeFileMIMETypeUnsupported is returned when a file MIME type is 95 // unsupoorted. 96 ErrorCodeFileMIMETypeUnsupported ErrorCodeT = 11 97 98 // ErrorCodeTokenInvalid is returned when a token is invalid. 99 ErrorCodeTokenInvalid ErrorCodeT = 12 100 101 // ErrorCodeRecordNotFound is returned when a record is not found. 102 ErrorCodeRecordNotFound ErrorCodeT = 13 103 104 // ErrorCodeRecordLocked is returned when a record is locked. 105 ErrorCodeRecordLocked ErrorCodeT = 14 106 107 // ErrorCodeNoRecordChanges is retuned when no record changes found. 108 ErrorCodeNoRecordChanges ErrorCodeT = 15 109 110 // ErrorCodeStatusChangeInvalid is returned when a record status change 111 // is invalid. 112 ErrorCodeStatusChangeInvalid ErrorCodeT = 16 113 114 // ErrorCodePluginIDInvalid is returned when a plugin ID is invalid. 115 ErrorCodePluginIDInvalid ErrorCodeT = 17 116 117 // ErrorCodePluginCmdInvalid is returned when a plugin cmd is invalid. 118 ErrorCodePluginCmdInvalid ErrorCodeT = 18 119 120 // ErrorCodePageSizeExceeded is returned when the request's page size 121 // exceeds the maximum page size of the request. 122 ErrorCodePageSizeExceeded ErrorCodeT = 19 123 124 // ErrorCodeRecordStateInvalid is returned when the provided state 125 // does not match the record state. 126 ErrorCodeRecordStateInvalid ErrorCodeT = 20 127 128 // ErrorCodeRecordStatusInvalid is returned when a record status is 129 // invalid. 130 ErrorCodeRecordStatusInvalid ErrorCodeT = 21 131 132 // ErrorCodeDuplicatePayload is returned when a duplicate payload is sent 133 // to a plugin, where it tries to write data that already exists. Timestamp 134 // data relies on the hash of the payload, therefore duplicate payloads are 135 // not allowed since they will cause collisions. 136 ErrorCodeDuplicatePayload ErrorCodeT = 22 137 138 // ErrorCodeLast is used by unit tests to verify that all error codes have 139 // a human readable entry in the ErrorCodes map. This error will never be 140 // returned. 141 ErrorCodeLast ErrorCodeT = 23 142 ) 143 144 var ( 145 // ErrorCodes contains the human readable error codes. 146 ErrorCodes = map[ErrorCodeT]string{ 147 ErrorCodeInvalid: "invalid error", 148 ErrorCodeRequestPayloadInvalid: "request payload invalid", 149 ErrorCodeChallengeInvalid: "invalid challenge", 150 ErrorCodeMetadataStreamInvalid: "metadata stream invalid", 151 ErrorCodeMetadataStreamDuplicate: "metadata stream duplicate", 152 ErrorCodeFilesEmpty: "files are empty", 153 ErrorCodeFileNameInvalid: "file name invalid", 154 ErrorCodeFileNameDuplicate: "file name is a duplicate", 155 ErrorCodeFileDigestInvalid: "file digest invalid", 156 ErrorCodeFilePayloadInvalid: "file payload invalid", 157 ErrorCodeFileMIMETypeInvalid: "file mime type invalid", 158 ErrorCodeFileMIMETypeUnsupported: "file mime type not supported", 159 ErrorCodeTokenInvalid: "token invalid", 160 ErrorCodeRecordNotFound: "record not found", 161 ErrorCodeRecordLocked: "record is locked", 162 ErrorCodeNoRecordChanges: "no record changes", 163 ErrorCodeStatusChangeInvalid: "status change invalid", 164 ErrorCodePluginIDInvalid: "pluguin id invalid", 165 ErrorCodePluginCmdInvalid: "plugin cmd invalid", 166 ErrorCodePageSizeExceeded: "page size exceeded", 167 ErrorCodeRecordStateInvalid: "record state invalid", 168 ErrorCodeRecordStatusInvalid: "record status invalid", 169 ErrorCodeDuplicatePayload: "duplicate payload", 170 } 171 ) 172 173 // UserErrorReply is the reply that the server returns when it encounters an 174 // error that is caused by something that the user did (malformed input, bad 175 // timing, etc). The HTTP status code will be 400. 176 type UserErrorReply struct { 177 ErrorCode ErrorCodeT `json:"errorcode"` 178 ErrorContext string `json:"errorcontext,omitempty"` 179 } 180 181 // Error satisfies the error interface. 182 func (e UserErrorReply) Error() string { 183 return fmt.Sprintf("user error code: %v", e.ErrorCode) 184 } 185 186 // PluginErrorReply is the reply that the server returns when it encounters 187 // a plugin error. The error code will be specific to the plugin. 188 type PluginErrorReply struct { 189 PluginID string `json:"pluginid"` 190 ErrorCode uint32 `json:"errorcode"` 191 ErrorContext string `json:"errorcontext,omitempty"` 192 } 193 194 // Error satisfies the error interface. 195 func (e PluginErrorReply) Error() string { 196 return fmt.Sprintf("plugin %v error code: %v", e.PluginID, e.ErrorCode) 197 } 198 199 // ServerErrorReply is the reply that the server returns when it encounters an 200 // unrecoverable error while executing a command. The HTTP status code will be 201 // 500 and the ErrorCode field will contain a UNIX timestamp that the user can 202 // provide to the server admin to track down the error details in the logs. 203 type ServerErrorReply struct { 204 ErrorCode int64 `json:"errorcode"` 205 } 206 207 // Error satisfies the error interface. 208 func (e ServerErrorReply) Error() string { 209 return fmt.Sprintf("server error: %v", e.ErrorCode) 210 } 211 212 // RecordStateT represents the state of a record. 213 type RecordStateT uint32 214 215 const ( 216 // RecordStateInvalid is an invalid record state. 217 RecordStateInvalid RecordStateT = 0 218 219 // RecordStateUnvetted indicates a record has not been made public. 220 RecordStateUnvetted RecordStateT = 1 221 222 // RecordStateVetted indicates a record has been made public. 223 RecordStateVetted RecordStateT = 2 224 225 // RecordStateLast is used for unit test validation of human readable 226 // errors. 227 RecordStateLast = 3 228 ) 229 230 var ( 231 // RecordStates contains the human readable record states. 232 RecordStates = map[RecordStateT]string{ 233 RecordStateInvalid: "invalid", 234 RecordStateUnvetted: "unvetted", 235 RecordStateVetted: "vetted", 236 } 237 ) 238 239 // RecordStatusT represents the status of a record. 240 type RecordStatusT uint32 241 242 const ( 243 // RecordStatusInvalid is an invalid status code. 244 RecordStatusInvalid RecordStatusT = 0 245 246 // RecordStatusUnreviewed indicates a record has not been made 247 // public yet. The state of an unreviewed record will always be 248 // unvetted. 249 RecordStatusUnreviewed RecordStatusT = 1 250 251 // RecordStatusPublic indicates a record has been made public. The 252 // state of a public record will always be vetted. 253 RecordStatusPublic RecordStatusT = 2 254 255 // RecordStatusCensored indicates a record has been censored. A 256 // censored record is locked from any further updates and all 257 // record content is permanently deleted. A censored record can 258 // have a state of either unvetted or vetted. 259 RecordStatusCensored RecordStatusT = 3 260 261 // RecordStatusArchived indicates a record has been archived. An 262 // archived record is locked from any further updates. An archived 263 // record can have a state of either unvetted or vetted. 264 RecordStatusArchived RecordStatusT = 4 265 266 // RecordStatusLast is used for unit test validation of human readable 267 // errors. 268 RecordStatusLast = 5 269 ) 270 271 var ( 272 // RecordStatuses contains the human readable record statuses. 273 RecordStatuses = map[RecordStatusT]string{ 274 RecordStatusInvalid: "invalid", 275 RecordStatusUnreviewed: "unreviewed", 276 RecordStatusPublic: "public", 277 RecordStatusCensored: "censored", 278 RecordStatusArchived: "archived", 279 } 280 ) 281 282 // MetadataStream describes a single metada stream. 283 type MetadataStream struct { 284 PluginID string `json:"pluginid"` // Plugin identity 285 StreamID uint32 `json:"streamid"` // Stream identity 286 Payload string `json:"payload"` // JSON encoded metadata 287 } 288 289 // File represents a record file. 290 type File struct { 291 Name string `json:"name"` // Basename of the file 292 MIME string `json:"mime"` // MIME type 293 Digest string `json:"digest"` // SHA256 of decoded Payload 294 Payload string `json:"payload"` // Base64 encoded file payload 295 } 296 297 const ( 298 // TokenSize is the size of a censorship record token in bytes. 299 TokenSize = 8 300 301 // ShortTokenLength is the length, in characters, of a hex encoded 302 // token that has been shortened to improved UX. Short tokens can 303 // be used to retrieve record data but cannot be used on any routes 304 // that write record data. 7 characters was chosen to match the git 305 // abbreviated commitment hash size. 306 ShortTokenLength = 7 307 ) 308 309 // CensorshipRecord contains cryptographic proof that a record was accepted for 310 // review by the server. The proof is verifiable by the client. 311 type CensorshipRecord struct { 312 // Token is a random censorship token that is generated by the 313 // server. It serves as a unique identifier for the record. 314 Token string `json:"token"` 315 316 // Merkle is the ordered merkle root of all files in the record. 317 Merkle string `json:"merkle"` 318 319 // Signature is the server signature of the Merkle+Token. 320 Signature string `json:"signature"` 321 } 322 323 // Record represents a record and all of its contents. 324 type Record struct { 325 State RecordStateT `json:"state"` // Record state 326 Status RecordStatusT `json:"status"` // Record status 327 Version uint32 `json:"version"` // Version of this record 328 Timestamp int64 `json:"timestamp"` // Last update 329 Metadata []MetadataStream `json:"metadata"` 330 Files []File `json:"files"` 331 332 CensorshipRecord CensorshipRecord `json:"censorshiprecord"` 333 } 334 335 // RecordNew creates a new record. It must include all files that are part of 336 // the record and it may contain optional metadata. 337 type RecordNew struct { 338 Challenge string `json:"challenge"` // Random challenge 339 Metadata []MetadataStream `json:"metadata,omitempty"` 340 Files []File `json:"files"` 341 } 342 343 // RecordNewReply is the reply to the RecordNew command. 344 type RecordNewReply struct { 345 Response string `json:"response"` // Challenge response 346 Record Record `json:"record"` 347 } 348 349 // RecordEdit edits and existing record. 350 // 351 // MDAppend appends metadata to a metadata stream. MDOverwrite overwrites a 352 // metadata stream. If the metadata stream does not exist yet for either of 353 // these arguments, a new metadata stream will be created. 354 // 355 // FilesAdd should include files that are being modified or added. FilesDel 356 // is the filenames of existing files that will be deleted. If a filename is 357 // provided in FilesDel that does not correspond to an actual record file, it 358 // will be ignored. 359 type RecordEdit struct { 360 Challenge string `json:"challenge"` // Random challenge 361 Token string `json:"token"` // Censorship token 362 MDAppend []MetadataStream `json:"mdappend,omitempty"` 363 MDOverwrite []MetadataStream `json:"mdoverwrite,omitempty"` 364 FilesAdd []File `json:"filesadd,omitempty"` 365 FilesDel []string `json:"filesdel,omitempty"` 366 } 367 368 // RecordEditReply is the reply to the RecordEdit command. 369 type RecordEditReply struct { 370 Response string `json:"response"` // Challenge response 371 Record Record `json:"record"` 372 } 373 374 // RecordEditMetadata edits the metadata of a record. 375 // 376 // MDAppend appends metadata to a metadata stream. MDOverwrite overwrites a 377 // metadata stream. If the metadata stream does not exist yet for either of 378 // these arguments, a new metadata stream will be created. 379 type RecordEditMetadata struct { 380 Challenge string `json:"challenge"` // Random challenge 381 Token string `json:"token"` // Censorship token 382 MDAppend []MetadataStream `json:"mdappend,omitempty"` 383 MDOverwrite []MetadataStream `json:"mdoverwrite,omitempty"` 384 } 385 386 // RecordEditMetadataReply is the reply to the RecordEditMetadata command. 387 type RecordEditMetadataReply struct { 388 Response string `json:"response"` // Challenge response 389 Record Record `json:"record"` 390 } 391 392 // RecordSetStatus sets the status of a record. 393 // 394 // MDAppend appends metadata to a metadata stream. MDOverwrite overwrites a 395 // metadata stream. If the metadata stream does not exist yet for either of 396 // these arguments, a new metadata stream will be created. 397 type RecordSetStatus struct { 398 Challenge string `json:"challenge"` // Random challenge 399 Token string `json:"token"` // Censorship token 400 Status RecordStatusT `json:"status"` 401 MDAppend []MetadataStream `json:"mdappend,omitempty"` 402 MDOverwrite []MetadataStream `json:"mdoverwrite,omitempty"` 403 } 404 405 // RecordSetStatusReply is the reply to the RecordSetStatus command. 406 type RecordSetStatusReply struct { 407 Response string `json:"response"` // Challenge response 408 Record Record `json:"record"` 409 } 410 411 // Proof contains an inclusion proof for the digest in the merkle root. All 412 // digests are hex encoded SHA256 digests. 413 // 414 // The ExtraData field is used by certain types of proofs to include additional 415 // data that is required to validate the proof. 416 type Proof struct { 417 Type string `json:"type"` 418 Digest string `json:"digest"` 419 MerkleRoot string `json:"merkleroot"` 420 MerklePath []string `json:"merklepath"` 421 ExtraData string `json:"extradata"` // JSON encoded 422 } 423 424 // Timestamp contains all of the data required to verify that a piece of record 425 // content was timestamped onto the decred blockchain. 426 // 427 // All digests are hex encoded SHA256 digests. The merkle root can be found in 428 // the OP_RETURN of the specified DCR transaction. 429 // 430 // TxID, MerkleRoot, and Proofs will only be populated once the merkle root has 431 // been included in a DCR tx and the tx has 6 confirmations. The Data field 432 // will not be populated if the data has been censored. 433 type Timestamp struct { 434 Data string `json:"data"` // JSON encoded 435 Digest string `json:"digest"` 436 TxID string `json:"txid"` 437 MerkleRoot string `json:"merkleroot"` 438 Proofs []Proof `json:"proofs"` 439 } 440 441 // RecordTimestamps requests the timestamps for a record. If a version is not 442 // included the most recent version will be returned. 443 type RecordTimestamps struct { 444 Challenge string `json:"challenge"` // Random challenge 445 Token string `json:"token"` // Censorship token 446 Version uint32 `json:"version,omitempty"` // Record version 447 } 448 449 // RecordTimestampsReply is the reply ot the RecordTimestamps command. 450 type RecordTimestampsReply struct { 451 Response string `json:"response"` // Challenge response 452 RecordMetadata Timestamp `json:"recordmetadata"` 453 454 // map[pluginID]map[streamID]Timestamp 455 Metadata map[string]map[uint32]Timestamp `json:"metadata"` 456 457 // map[filename]Timestamp 458 Files map[string]Timestamp `json:"files"` 459 } 460 461 const ( 462 // RecordsPageSize is the maximum number of records that can be 463 // requested using the Records commands. 464 RecordsPageSize uint32 = 5 465 ) 466 467 // RecordRequest is used to request a record. It gives the caller granular 468 // control over what is returned. The only required field is the token. All 469 // other fields are optional. All record files are returned by default unless 470 // one of the file arguments is provided. 471 // 472 // Version is used to request a specific version of a record. If no version is 473 // provided then the most recent version of the record will be returned. 474 // 475 // Filenames can be used to request specific files. If filenames is provided 476 // then the specified files will be the only files that are returned. 477 // 478 // OmitAllFiles can be used to retrieve a record without any of the record 479 // files. This supersedes the filenames argument. 480 type RecordRequest struct { 481 Token string `json:"token"` 482 Version uint32 `json:"version,omitempty"` 483 Filenames []string `json:"filenames,omitempty"` 484 OmitAllFiles bool `json:"omitallfiles,omitempty"` 485 } 486 487 // Records retrieves a record. If no version is provided the most recent 488 // version will be returned. 489 type Records struct { 490 Challenge string `json:"challenge"` // Random challenge 491 Requests []RecordRequest `json:"requests"` 492 } 493 494 // RecordsReply is the reply to the Records command. If a record was not found 495 // or an error occurred while retrieving it the token will not be included in 496 // the returned map. 497 // 498 // **Note** partial record's merkle root is not verifiable - when generating 499 // the record's merkle all files must be present. 500 type RecordsReply struct { 501 Response string `json:"response"` // Challenge response 502 Records map[string]Record `json:"records"` // [token]Record 503 } 504 505 const ( 506 // InventoryPageSize is the number of tokens that will be returned 507 // per page for all inventory commands. 508 InventoryPageSize uint32 = 20 509 ) 510 511 // Inventory requests the tokens of the records in the inventory, categorized 512 // by record state and record status. The tokens are ordered by the timestamp 513 // of their most recent status change, sorted from newest to oldest. 514 // 515 // The state, status, and page arguments can be provided to request a specific 516 // page of record tokens. 517 // 518 // If no status is provided then a page of tokens for all statuses will be 519 // returned. All other arguments will be ignored. 520 type Inventory struct { 521 Challenge string `json:"challenge"` // Random challenge 522 State RecordStateT `json:"state,omitempty"` 523 Status RecordStatusT `json:"status,omitempty"` 524 Page uint32 `json:"page,omitempty"` 525 } 526 527 // InventoryReply is the reply to the Inventory command. The map keys are the 528 // human readable record statuses defined by the RecordStatuses array. 529 type InventoryReply struct { 530 Response string `json:"response"` // Challenge response 531 Unvetted map[string][]string `json:"unvetted"` // [status][]token 532 Vetted map[string][]string `json:"vetted"` // [status][]token 533 } 534 535 // InventoryOrdered requests a page of record tokens ordered by the timestamp 536 // of their most recent status change from newest to oldest. The reply will 537 // include tokens for all record statuses. 538 type InventoryOrdered struct { 539 Challenge string `json:"challenge"` // Random challenge 540 State RecordStateT `json:"state"` 541 Page uint32 `json:"page"` 542 } 543 544 // InventoryOrderedReply is the reply to the InventoryOrdered command. 545 type InventoryOrderedReply struct { 546 Response string `json:"response"` // Challenge response 547 Tokens []string `json:"tokens"` 548 } 549 550 // PluginCmd represents plugin command and the command payload. A token is 551 // required for all plugin writes, but is optional for reads. 552 type PluginCmd struct { 553 Token string `json:"token,omitempty"` // Censorship token 554 ID string `json:"id"` // Plugin identifier 555 Command string `json:"command"` // Plugin command 556 Payload string `json:"payload,omitempty"` // Command payload 557 } 558 559 // PluginWrite executes a plugin command that writes data. 560 type PluginWrite struct { 561 Challenge string `json:"challenge"` // Random challenge 562 Cmd PluginCmd `json:"cmd"` 563 } 564 565 // PluginWriteReply is the reply to the PluginWrite command. 566 type PluginWriteReply struct { 567 Response string `json:"response"` // Challenge response 568 Payload string `json:"payload"` // Response payload 569 } 570 571 // PluginReads executes a batch of read only plugin commands. 572 type PluginReads struct { 573 Challenge string `json:"challenge"` // Random challenge 574 Cmds []PluginCmd `json:"cmds"` 575 } 576 577 // PluginCmdReply is the reply to an individual plugin command that is part of 578 // a batch of plugin commands. The error will be included in the reply if one 579 // was encountered. 580 type PluginCmdReply struct { 581 Token string `json:"token"` // Censorship token 582 ID string `json:"id"` // Plugin identifier 583 Command string `json:"command"` // Plugin command 584 Payload string `json:"payload"` // Response payload 585 586 // UserError will be populated if a user error is encountered prior 587 // to plugin command execution. 588 UserError *UserErrorReply `json:"usererror,omitempty"` 589 590 // PluginError will be populated if a plugin error occurred during 591 // plugin command execution. 592 PluginError *PluginErrorReply `json:"pluginerror,omitempty"` 593 } 594 595 // PluginReadsReply is the reply to the PluginReads command. 596 type PluginReadsReply struct { 597 Response string `json:"response"` // Challenge response 598 Replies []PluginCmdReply `json:"replies"` 599 } 600 601 // PluginSetting is a structure that holds key/value pairs of a plugin setting. 602 type PluginSetting struct { 603 Key string `json:"key"` 604 Value string `json:"value"` 605 } 606 607 // Plugin describes a plugin and its settings. 608 type Plugin struct { 609 ID string `json:"id"` 610 Settings []PluginSetting `json:"settings"` 611 } 612 613 // PluginInventory retrieves all active plugins and their settings. 614 type PluginInventory struct { 615 Challenge string `json:"challenge"` // Random challenge 616 } 617 618 // PluginInventoryReply returns all plugins and their settings. 619 type PluginInventoryReply struct { 620 Response string `json:"response"` // Challenge response 621 Plugins []Plugin `json:"plugins"` 622 }