github.com/decred/politeia@v1.4.0/politeiad/plugins/comments/comments.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 comments provides a plugin for extending a record with comment
     6  // functionality.
     7  package comments
     8  
     9  const (
    10  	// PluginID is the unique identifier for this plugin.
    11  	PluginID = "comments"
    12  
    13  	// Plugin commands
    14  	CmdNew        = "new"        // Create a new comment
    15  	CmdEdit       = "edit"       // Edit a comment
    16  	CmdDel        = "del"        // Del a comment
    17  	CmdVote       = "vote"       // Vote on a comment
    18  	CmdGet        = "get"        // Get specified comments
    19  	CmdGetAll     = "getall"     // Get all comments for a record
    20  	CmdGetVersion = "getversion" // Get specified version of a comment
    21  	CmdCount      = "count"      // Get comments count for a record
    22  	CmdVotes      = "votes"      // Get comment votes
    23  	CmdTimestamps = "timestamps" // Get timestamps
    24  )
    25  
    26  // Plugin setting keys can be used to specify custom plugin settings. Default
    27  // plugin setting values can be overridden by providing a plugin setting key
    28  // and value to the plugin on startup.
    29  const (
    30  	// SettingKeyCommentLengthMax is the plugin setting key for the
    31  	// SettingCommentLengthMax plugin setting.
    32  	SettingKeyCommentLengthMax = "commentlengthmax"
    33  
    34  	// SettingKeyVoteChangesMax is the plugin setting key for the
    35  	// SettingVoteChangesMax plugin setting.
    36  	SettingKeyVoteChangesMax = "votechangesmax"
    37  
    38  	// SettingKeyAllowExtraData is the plugin setting key for the
    39  	// SettingAllowExtraData plugin setting.
    40  	SettingKeyAllowExtraData = "allowextradata"
    41  
    42  	// SettingKeyVotesPageSize is the plugin setting key for the
    43  	// SettingVotesPageSize plugin setting.
    44  	SettingKeyVotesPageSize = "votespagesize"
    45  
    46  	// SettingKeyCountPageSize is the plugin setting key for the
    47  	// SettingCountPageSize plugin setting.
    48  	SettingKeyCountPageSize = "countpagesize"
    49  
    50  	// SettingKeyTimestampsPageSize is the plugin setting key for the
    51  	// SettingTimestampsPageSize plugin setting.
    52  	SettingKeyTimestampsPageSize = "timestampspagesize"
    53  
    54  	// SettingKeyAllowEdits is the plugin setting key for the
    55  	// SettingAllowEdits plugin setting.
    56  	SettingKeyAllowEdits = "allowedits"
    57  
    58  	// SettingKeyEditPeriod is the plugin setting key for the
    59  	// SettingEditPeriod plugin setting.
    60  	SettingKeyEditPeriod = "editperiod"
    61  )
    62  
    63  // Plugin setting default values. These can be overridden by providing a
    64  // plugin setting key and value to the plugin on startup.
    65  const (
    66  	// SettingCommentLengthMax is the default maximum number of
    67  	// characters that are allowed in a comment.
    68  	SettingCommentLengthMax uint32 = 8000
    69  
    70  	// SettingVoteChangesMax is the default maximum number of times a
    71  	// user can change their vote on a comment. This prevents a
    72  	// malicious user from being able to spam comment votes.
    73  	SettingVoteChangesMax uint32 = 5
    74  
    75  	// SettingAllowExtraData is the default value of the bool flag which
    76  	// determines whether posting extra data along with the comment is allowed.
    77  	SettingAllowExtraData = false
    78  
    79  	// SettingVotesPageSize is the default maximum number of comment votes
    80  	// that can be returned at any one time. It defaults to 2500 to limit the
    81  	// comment votes route payload size to be ~1MiB, as each comment vote size
    82  	// is expected to be around 400 bytes which means:
    83  	// 2500 * 400 byte = 1000000 byte = ~1MiB.
    84  	SettingVotesPageSize uint32 = 2500
    85  
    86  	// SettingCountPageSize is the default maximum number of comment counts
    87  	// that can be requested at any one time.
    88  	SettingCountPageSize uint32 = 10
    89  
    90  	// SettingTimestampsPageSize is the default maximum number of comment
    91  	// timestamps that can be requested at any one time.
    92  	SettingTimestampsPageSize uint32 = 100
    93  
    94  	// SettingAllowEdits is the default value of the bool flag which
    95  	// determines whether comment edits are temporarily allowed during the
    96  	// timeframe set by SettingEditPeriod.
    97  	SettingAllowEdits = false
    98  
    99  	// SettingEditPeriod is the default maximum amount of time,
   100  	// in seconds, since the submission of a comment where it's still
   101  	// editable. It defaults to five minutes which should be enough time
   102  	// to spot typos and grammar mistakes.
   103  	SettingEditPeriod uint32 = 300
   104  )
   105  
   106  // ErrorCodeT represents a error that was caused by the user.
   107  type ErrorCodeT uint32
   108  
   109  const (
   110  	// ErrorCodeInvalid is an invalid error code.
   111  	ErrorCodeInvalid ErrorCodeT = 0
   112  
   113  	// ErrorCodeTokenInvalid is returned when a token is invalid.
   114  	ErrorCodeTokenInvalid ErrorCodeT = 1
   115  
   116  	// ErrorCodePublicKeyInvalid is returned when a public key is
   117  	// invalid.
   118  	ErrorCodePublicKeyInvalid ErrorCodeT = 2
   119  
   120  	// ErrorCodeSignatureInvalid is returned when a signature is
   121  	// invalid.
   122  	ErrorCodeSignatureInvalid ErrorCodeT = 3
   123  
   124  	// ErrorCodeMaxLengthExceeded is returned when a comment exceeds the
   125  	// max length plugin setting.
   126  	ErrorCodeMaxLengthExceeded ErrorCodeT = 4
   127  
   128  	// ErrorCodeNoChanges is returned when a comment edit does not
   129  	// contain any changes.
   130  	ErrorCodeNoChanges ErrorCodeT = 5
   131  
   132  	// ErrorCodeCommentNotFound is returned when a comment could not be
   133  	// found.
   134  	ErrorCodeCommentNotFound ErrorCodeT = 6
   135  
   136  	// ErrorCodeUserUnauthorized is returned when a user is attempting
   137  	// to edit a comment that they did not submit.
   138  	ErrorCodeUserUnauthorized ErrorCodeT = 7
   139  
   140  	// ErrorCodeParentIDInvalid is returned when a comment parent ID
   141  	// does not correspond to an actual comment.
   142  	ErrorCodeParentIDInvalid ErrorCodeT = 8
   143  
   144  	// ErrorCodeVoteInvalid is returned when a comment vote is invalid.
   145  	ErrorCodeVoteInvalid ErrorCodeT = 9
   146  
   147  	// ErrorCodeVoteChangesMaxExceeded is returned when the number of
   148  	// times the user has changed their vote has exceeded the vote
   149  	// changes max plugin setting.
   150  	ErrorCodeVoteChangesMaxExceeded ErrorCodeT = 10
   151  
   152  	// ErrorCodeRecordStateInvalid is returned when the provided state
   153  	// does not match the record state.
   154  	ErrorCodeRecordStateInvalid ErrorCodeT = 11
   155  
   156  	// ErrorCodeExtraDataNotAllowed is returned when comment extra data
   157  	// is found while comment plugin setting does not allow it.
   158  	ErrorCodeExtraDataNotAllowed = 12
   159  
   160  	// ErrorCodeEditNotAllowed is returned when comment edit is not
   161  	// allowed.
   162  	ErrorCodeEditNotAllowed = 13
   163  
   164  	// ErrorCodeEmptyComment is returned when a comment with no comment text
   165  	// is submitted.
   166  	ErrorCodeEmptyComment = 14
   167  
   168  	// ErrorCodeLast is used by unit tests to verify that all error codes have
   169  	// a human readable entry in the ErrorCodes map. This error code will never
   170  	// be returned.
   171  	ErrorCodeLast ErrorCodeT = 15
   172  )
   173  
   174  var (
   175  	// ErrorCodes contains the human readable error messages.
   176  	ErrorCodes = map[ErrorCodeT]string{
   177  		ErrorCodeInvalid:                "error code invalid",
   178  		ErrorCodeTokenInvalid:           "token invalid",
   179  		ErrorCodePublicKeyInvalid:       "public key invalid",
   180  		ErrorCodeSignatureInvalid:       "signature invalid",
   181  		ErrorCodeMaxLengthExceeded:      "max length exceeded",
   182  		ErrorCodeNoChanges:              "no changes",
   183  		ErrorCodeCommentNotFound:        "comment not found",
   184  		ErrorCodeUserUnauthorized:       "user unauthorized",
   185  		ErrorCodeParentIDInvalid:        "parent id invalid",
   186  		ErrorCodeVoteInvalid:            "vote invalid",
   187  		ErrorCodeVoteChangesMaxExceeded: "vote changes max exceeded",
   188  		ErrorCodeRecordStateInvalid:     "record state invalid",
   189  		ErrorCodeExtraDataNotAllowed:    "comment extra data not allowed",
   190  		ErrorCodeEditNotAllowed:         "comment edit is not allowed",
   191  		ErrorCodeEmptyComment:           "comment is empty",
   192  	}
   193  )
   194  
   195  // RecordStateT represents the state of a record.
   196  type RecordStateT uint32
   197  
   198  const (
   199  	// RecordStateInvalid is an invalid record state.
   200  	RecordStateInvalid RecordStateT = 0
   201  
   202  	// RecordStateUnvetted indicates a record has not been made public.
   203  	RecordStateUnvetted RecordStateT = 1
   204  
   205  	// RecordStateVetted indicates a record has been made public.
   206  	RecordStateVetted RecordStateT = 2
   207  )
   208  
   209  // Comment represent a record comment.
   210  //
   211  // A parent ID of 0 indicates that the comment is a base level comment and not
   212  // a reply commment.
   213  //
   214  // Comments made on a record when it is unvetted and when it is vetted are
   215  // treated as two distinct groups of comments. When a record becomes vetted the
   216  // comment ID starts back at 1.
   217  //
   218  // If a comment is deleted the PublicKey, Signature, Receipt, and Timestamp
   219  // fields will be the from the deletion action, not from the original comment.
   220  // The only field that is retained from the original comment is the UserID
   221  // field so that the client can still display the correct user information for
   222  // the deleted comment. Everything else from the original comment is
   223  // permanently deleted.
   224  //
   225  // PublicKey is the user's public key that is used to verify the signature.
   226  //
   227  // Signature is the user signature of the:
   228  // State + Token + ParentID + Comment + ExtraData + ExtraDataHint
   229  //
   230  // Receipt is the server signature of the user signature.
   231  //
   232  // The PublicKey, Signature, and Receipt are all hex encoded and use the
   233  // ed25519 signature scheme.
   234  type Comment struct {
   235  	UserID    string       `json:"userid"`    // Unique user ID
   236  	State     RecordStateT `json:"state"`     // Record state
   237  	Token     string       `json:"token"`     // Record token
   238  	ParentID  uint32       `json:"parentid"`  // Parent comment ID if reply
   239  	Comment   string       `json:"comment"`   // Comment text
   240  	PublicKey string       `json:"publickey"` // Public key used for Signature
   241  	Signature string       `json:"signature"` // Client signature
   242  	CommentID uint32       `json:"commentid"` // Comment ID
   243  	Version   uint32       `json:"version"`   // Comment version
   244  	CreatedAt int64        `json:"createdat"` // UNIX timestamp of creation time
   245  	Timestamp int64        `json:"timestamp"` // UNIX timestamp of last edit
   246  	Receipt   string       `json:"receipt"`   // Server sig of client sig
   247  	Downvotes uint64       `json:"downvotes"` // Tolal downvotes on comment
   248  	Upvotes   uint64       `json:"upvotes"`   // Total upvotes on comment
   249  
   250  	Deleted bool   `json:"deleted,omitempty"` // Comment has been deleted
   251  	Reason  string `json:"reason,omitempty"`  // Reason for deletion
   252  
   253  	// Optional fields to be used freely
   254  	ExtraData     string `json:"extradata,omitempty"`
   255  	ExtraDataHint string `json:"extradatahint,omitempty"`
   256  }
   257  
   258  // CommentAdd is the structure that is saved to disk when a comment is created
   259  // or edited.
   260  //
   261  // PublicKey is the user's public key that is used to verify the signature.
   262  //
   263  // The structure of the signature field depends on whether the CommentAdd is
   264  // associated with a new comment or a comment edit:
   265  //
   266  //  1. When a comment is created it's the user signature of the:
   267  //     State + Token + ParentID + Comment + ExtraData + ExtraDataHint.
   268  //
   269  //  2. When a comment is edited it's the user signature of the:
   270  //     State + Token + ParentID + CommentID + Comment + ExtraData + ExtraDataHint.
   271  //
   272  // Receipt is the server signature of the user signature.
   273  //
   274  // The PublicKey, Signature, and Receipt are all hex encoded and use the
   275  // ed25519 signature scheme.
   276  type CommentAdd struct {
   277  	// Data generated by client
   278  	UserID    string       `json:"userid"`    // Unique user ID
   279  	State     RecordStateT `json:"state"`     // Record state
   280  	Token     string       `json:"token"`     // Record token
   281  	ParentID  uint32       `json:"parentid"`  // Parent comment ID
   282  	Comment   string       `json:"comment"`   // Comment
   283  	PublicKey string       `json:"publickey"` // Pubkey used for Signature
   284  	Signature string       `json:"signature"` // Client signature
   285  
   286  	// Metadata generated by server
   287  	CommentID uint32 `json:"commentid"` // Comment ID
   288  	Version   uint32 `json:"version"`   // Comment version
   289  	Timestamp int64  `json:"timestamp"` // Received UNIX timestamp
   290  	Receipt   string `json:"receipt"`   // Server signature of client signature
   291  
   292  	// Optional fields to be used freely
   293  	ExtraData     string `json:"extradata,omitempty"`
   294  	ExtraDataHint string `json:"extradatahint,omitempty"`
   295  }
   296  
   297  // CommentDel is the structure that is saved to disk when a comment is deleted.
   298  // Some additional fields like ParentID and UserID are required to be saved
   299  // since all the CommentAdd records will be deleted and the client needs these
   300  // additional fields to properly display the deleted comment in the comment
   301  // hierarchy.
   302  //
   303  // PublicKey is the user's public key that is used to verify the signature.
   304  //
   305  // Signature is the user signature of the:
   306  // State + Token + CommentID + Reason
   307  //
   308  // The PublicKey and Signature are hex encoded and use the
   309  // ed25519 signature scheme.
   310  type CommentDel struct {
   311  	// Data generated by client
   312  	Token     string       `json:"token"`     // Record token
   313  	State     RecordStateT `json:"state"`     // Record state
   314  	CommentID uint32       `json:"commentid"` // Comment ID
   315  	Reason    string       `json:"reason"`    // Reason for deleting
   316  	PublicKey string       `json:"publickey"` // Pubkey used for Signature
   317  	Signature string       `json:"signature"` // Client signature
   318  
   319  	// Metadata generated by server
   320  	ParentID  uint32 `json:"parentid"`  // Parent comment ID
   321  	UserID    string `json:"userid"`    // Author user ID
   322  	Timestamp int64  `json:"timestamp"` // Received UNIX timestamp
   323  	Receipt   string `json:"receipt"`   // Server sig of client sig
   324  }
   325  
   326  // VoteT represents a comment upvote/downvote.
   327  type VoteT int32
   328  
   329  const (
   330  	// VoteInvalid is an invalid comment vote.
   331  	VoteInvalid VoteT = 0
   332  
   333  	// VoteDownvote represents a comment downvote.
   334  	VoteDownvote VoteT = -1
   335  
   336  	// VoteUpvote represents a comment upvote.
   337  	VoteUpvote VoteT = 1
   338  )
   339  
   340  // CommentVote is the structure that is saved to disk when a comment is voted
   341  // on.
   342  //
   343  // PublicKey is the user's public key that is used to verify the signature.
   344  //
   345  // Signature is the user signature of the:
   346  // State + Token + CommentID + Vote
   347  //
   348  // The PublicKey and Signature are hex encoded and use the
   349  // ed25519 signature scheme.
   350  type CommentVote struct {
   351  	// Data generated by client
   352  	UserID    string       `json:"userid"`    // Unique user ID
   353  	State     RecordStateT `json:"state"`     // Record state
   354  	Token     string       `json:"token"`     // Record token
   355  	CommentID uint32       `json:"commentid"` // Comment ID
   356  	Vote      VoteT        `json:"vote"`      // Upvote or downvote
   357  	PublicKey string       `json:"publickey"` // Public key used for signature
   358  	Signature string       `json:"signature"` // Client signature
   359  
   360  	// Metadata generated by server
   361  	Timestamp int64  `json:"timestamp"` // Received UNIX timestamp
   362  	Receipt   string `json:"receipt"`   // Server signature of client signature
   363  }
   364  
   365  // New creates a new comment.
   366  //
   367  // The parent ID is used to reply to an existing comment. A parent ID of 0
   368  // indicates that the comment is a base level comment and not a reply commment.
   369  //
   370  // PublicKey is the user's public key that is used to verify the signature.
   371  //
   372  // Signature is the user signature of the:
   373  // State + Token + ParentID + Comment + ExtraData + ExtraDataHint
   374  //
   375  // Receipt is the server signature of the user signature.
   376  //
   377  // The PublicKey, Signature, and Receipt are all hex encoded and use the
   378  // ed25519 signature scheme.
   379  type New struct {
   380  	UserID    string       `json:"userid"`    // Unique user ID
   381  	State     RecordStateT `json:"state"`     // Record state
   382  	Token     string       `json:"token"`     // Record token
   383  	ParentID  uint32       `json:"parentid"`  // Parent comment ID
   384  	Comment   string       `json:"comment"`   // Comment text
   385  	PublicKey string       `json:"publickey"` // Pubkey used for Signature
   386  	Signature string       `json:"signature"` // Client signature
   387  
   388  	// Optional fields to be used freely
   389  	ExtraData     string `json:"extradata,omitempty"`
   390  	ExtraDataHint string `json:"extradatahint,omitempty"`
   391  }
   392  
   393  // NewReply is the reply to the New command.
   394  type NewReply struct {
   395  	Comment Comment `json:"comment"`
   396  }
   397  
   398  // Edit edits an existing comment.
   399  //
   400  // PublicKey is the user's public key that is used to verify the signature.
   401  //
   402  // Signature is the user signature of the:
   403  // State + Token + ParentID + CommentID + Comment + ExtraData + ExtraDataHint
   404  //
   405  // Receipt is the server signature of the user signature.
   406  //
   407  // The PublicKey, Signature, and Receipt are all hex encoded and use the
   408  // ed25519 signature scheme.
   409  type Edit struct {
   410  	UserID    string       `json:"userid"`    // Unique user ID
   411  	State     RecordStateT `json:"state"`     // Record state
   412  	Token     string       `json:"token"`     // Record token
   413  	ParentID  uint32       `json:"parentid"`  // Parent comment ID
   414  	CommentID uint32       `json:"commentid"` // Comment ID
   415  	Comment   string       `json:"comment"`   // Comment text
   416  	PublicKey string       `json:"publickey"` // Pubkey used for Signature
   417  	Signature string       `json:"signature"` // Client signature
   418  
   419  	// Optional fields to be used freely
   420  	ExtraData     string `json:"extradata,omitempty"`
   421  	ExtraDataHint string `json:"extradatahint,omitempty"`
   422  }
   423  
   424  // EditReply is the reply to the Edit command.
   425  type EditReply struct {
   426  	Comment Comment `json:"comment"`
   427  }
   428  
   429  // Del permanently deletes all versions of the provided comment.
   430  //
   431  // PublicKey is the user's public key that is used to verify the signature.
   432  //
   433  // Signature is the user signature of the:
   434  // State + Token + CommentID + Reason
   435  //
   436  // The PublicKey and Signature are hex encoded and use the
   437  // ed25519 signature scheme.
   438  type Del struct {
   439  	State     RecordStateT `json:"state"`     // Record state
   440  	Token     string       `json:"token"`     // Record token
   441  	CommentID uint32       `json:"commentid"` // Comment ID
   442  	Reason    string       `json:"reason"`    // Reason for deletion
   443  	PublicKey string       `json:"publickey"` // Public key used for signature
   444  	Signature string       `json:"signature"` // Client signature
   445  }
   446  
   447  // DelReply is the reply to the Del command.
   448  type DelReply struct {
   449  	Comment Comment `json:"comment"`
   450  }
   451  
   452  // Vote casts a comment vote (upvote or downvote).
   453  //
   454  // The effect of a new vote on a comment score depends on the previous vote
   455  // from that user ID. Example, a user upvotes a comment that they have already
   456  // upvoted, the resulting vote score is 0 due to the second upvote removing the
   457  // original upvote. The public key cannot be relied on to remain the same for
   458  // each user so a user ID must be included.
   459  //
   460  // PublicKey is the user's public key that is used to verify the signature.
   461  //
   462  // Signature is the user signature of the:
   463  // State + Token + CommentID + Vote
   464  //
   465  // The PublicKey and Signature are hex encoded and use the
   466  // ed25519 signature scheme.
   467  type Vote struct {
   468  	UserID    string       `json:"userid"`    // Unique user ID
   469  	State     RecordStateT `json:"state"`     // Record state
   470  	Token     string       `json:"token"`     // Record token
   471  	CommentID uint32       `json:"commentid"` // Comment ID
   472  	Vote      VoteT        `json:"vote"`      // Upvote or downvote
   473  	PublicKey string       `json:"publickey"` // Public key used for signature
   474  	Signature string       `json:"signature"` // Client signature
   475  }
   476  
   477  // VoteReply is the reply to the Vote command.
   478  type VoteReply struct {
   479  	Downvotes uint64 `json:"downvotes"` // Tolal downvotes on comment
   480  	Upvotes   uint64 `json:"upvotes"`   // Total upvotes on comment
   481  	Timestamp int64  `json:"timestamp"` // Received UNIX timestamp
   482  	Receipt   string `json:"receipt"`   // Server signature of client signature
   483  }
   484  
   485  // Get retrieves a batch of specified comments. The most recent version of each
   486  // comment is returned. An error is not returned if a comment is not found for
   487  // one or more of the comment IDs. Those entries will simply not be included in
   488  // the reply.
   489  type Get struct {
   490  	CommentIDs []uint32 `json:"commentids"`
   491  }
   492  
   493  // GetReply is the reply to the Get command. The returned map will not include
   494  // an entry for any comment IDs that did not correspond to an actual comment.
   495  // It is the responsibility of the caller to ensure that a comment was returned
   496  // for all of the provided comment IDs.
   497  type GetReply struct {
   498  	Comments map[uint32]Comment `json:"comments"` // [commentID]Comment
   499  }
   500  
   501  // GetAll retrieves all comments for a record. The latest version of each
   502  // comment is returned.
   503  type GetAll struct{}
   504  
   505  // GetAllReply is the reply to the GetAll command. The returned comments array
   506  // is ordered by comment ID from smallest to largest.
   507  type GetAllReply struct {
   508  	Comments []Comment `json:"comments"`
   509  }
   510  
   511  // GetVersion retrieves the specified version of a comment.
   512  type GetVersion struct {
   513  	CommentID uint32 `json:"commentid"`
   514  	Version   uint32 `json:"version"`
   515  }
   516  
   517  // GetVersionReply is the reply to the GetVersion command.
   518  type GetVersionReply struct {
   519  	Comment Comment `json:"comment"`
   520  }
   521  
   522  // Count retrieves the comments count for a record. The comments count is the
   523  // number of comments that have been made on a record.
   524  type Count struct{}
   525  
   526  // CountReply is the reply to the Count command.
   527  type CountReply struct {
   528  	Count uint32 `json:"count"`
   529  }
   530  
   531  // Votes retrieves the record's comment votes that meet the provided filtering
   532  // criteria. If no filtering criteria is provided then it rerieves all comment
   533  // votes. This command is paginated, if no page is provided, then the first
   534  // page is returned. If the requested page does not exist an empty page
   535  // is returned.
   536  type Votes struct {
   537  	UserID string `json:"userid,omitempty"`
   538  	Page   uint32 `json:"page,omitempty"`
   539  }
   540  
   541  // VotesReply is the reply to the Votes command.
   542  type VotesReply struct {
   543  	Votes []CommentVote `json:"votes"`
   544  }
   545  
   546  // Proof contains an inclusion proof for the digest in the merkle root. The
   547  // ExtraData field is used by certain types of proofs to include additional
   548  // data that is required to validate the proof.
   549  type Proof struct {
   550  	Type       string   `json:"type"`
   551  	Digest     string   `json:"digest"`
   552  	MerkleRoot string   `json:"merkleroot"`
   553  	MerklePath []string `json:"merklepath"`
   554  	ExtraData  string   `json:"extradata"` // JSON encoded
   555  }
   556  
   557  // Timestamp contains all of the data required to verify that a piece of data
   558  // was timestamped onto the decred blockchain.
   559  //
   560  // All digests are hex encoded SHA256 digests. The merkle root can be found in
   561  // the OP_RETURN of the specified DCR transaction.
   562  //
   563  // TxID, MerkleRoot, and Proofs will only be populated once the merkle root has
   564  // been included in a DCR tx and the tx has 6 confirmations. The Data field
   565  // will not be populated if the data has been censored.
   566  type Timestamp struct {
   567  	Data       string  `json:"data"` // JSON encoded
   568  	Digest     string  `json:"digest"`
   569  	TxID       string  `json:"txid"`
   570  	MerkleRoot string  `json:"merkleroot"`
   571  	Proofs     []Proof `json:"proofs"`
   572  }
   573  
   574  // CommentTimestamp contains the timestamps for the full history of a single
   575  // comment.
   576  //
   577  // A CommentAdd is the structure that is saved to disk anytime a comment is
   578  // created or edited. This structure is what will be timestamped.  The data
   579  // payload of a timestamp in the Adds field will contain a JSON encoded
   580  // CommentAdd.
   581  //
   582  // A CommentDel is the structure that is saved to disk anytime a comment is
   583  // deleted. This structure is what will be timestamped. The data payload of a
   584  // timestamp in the Del field will contain a JSON encoded CommentDel.
   585  //
   586  // A CommentVote is the structure that is saved to disk anytime a comment is
   587  // voted on. This structure is what will be timestamped. The data payload of
   588  // a timestamp in the Votes filed will contain a JSON encoded CommentVote.
   589  type CommentTimestamp struct {
   590  	Adds  []Timestamp `json:"adds"`
   591  	Del   *Timestamp  `json:"del,omitempty"`
   592  	Votes []Timestamp `json:"votes,omitempty"`
   593  }
   594  
   595  // Timestamps retrieves the timestamps for a record's comments. If a requested
   596  // comment ID does not exist, it will not be included in the reply. An error is
   597  // not returned.
   598  //
   599  // If IncludeVotes is set to true then the timestamps for the comment votes
   600  // will also be returned.
   601  type Timestamps struct {
   602  	CommentIDs   []uint32 `json:"commentids"`
   603  	IncludeVotes bool     `json:"includevotes,omitempty"`
   604  }
   605  
   606  // TimestampsReply is the reply to the timestamps command.
   607  type TimestampsReply struct {
   608  	Comments map[uint32]CommentTimestamp `json:"comments"`
   609  }