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  }