github.com/decred/politeia@v1.4.0/politeiawww/legacy/ticketvote/process.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 ticketvote
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  
    11  	"github.com/decred/politeia/politeiad/plugins/ticketvote"
    12  	v1 "github.com/decred/politeia/politeiawww/api/ticketvote/v1"
    13  	"github.com/decred/politeia/politeiawww/legacy/user"
    14  )
    15  
    16  func (t *TicketVote) processAuthorize(ctx context.Context, a v1.Authorize, u user.User) (*v1.AuthorizeReply, error) {
    17  	log.Tracef("processAuthorize: %v", a.Token)
    18  
    19  	// Verify user signed with their active identity
    20  	if u.PublicKey() != a.PublicKey {
    21  		return nil, v1.UserErrorReply{
    22  			ErrorCode:    v1.ErrorCodePublicKeyInvalid,
    23  			ErrorContext: "not active identity",
    24  		}
    25  	}
    26  
    27  	// Verify user is the record author
    28  	authorID, err := t.politeiad.Author(ctx, a.Token)
    29  	if err != nil {
    30  		return nil, err
    31  	}
    32  	if u.ID.String() != authorID {
    33  		return nil, v1.UserErrorReply{
    34  			ErrorCode:    v1.ErrorCodeUnauthorized,
    35  			ErrorContext: "user is not record author",
    36  		}
    37  	}
    38  
    39  	// Send plugin command
    40  	ta := ticketvote.Authorize{
    41  		Token:     a.Token,
    42  		Version:   a.Version,
    43  		Action:    ticketvote.AuthActionT(a.Action),
    44  		PublicKey: a.PublicKey,
    45  		Signature: a.Signature,
    46  	}
    47  	tar, err := t.politeiad.TicketVoteAuthorize(ctx, ta)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  
    52  	// Emit event
    53  	t.events.Emit(EventTypeAuthorize,
    54  		EventAuthorize{
    55  			Auth: a,
    56  			User: u,
    57  		})
    58  
    59  	return &v1.AuthorizeReply{
    60  		Timestamp: tar.Timestamp,
    61  		Receipt:   tar.Receipt,
    62  	}, nil
    63  }
    64  
    65  func (t *TicketVote) processStart(ctx context.Context, s v1.Start, u user.User) (*v1.StartReply, error) {
    66  	log.Tracef("processStart: %v", len(s.Starts))
    67  
    68  	// Verify there is work to be done
    69  	if len(s.Starts) == 0 {
    70  		return nil, v1.UserErrorReply{
    71  			ErrorCode:    v1.ErrorCodeInputInvalid,
    72  			ErrorContext: "no start details found",
    73  		}
    74  	}
    75  
    76  	// Verify user signed with their active identity
    77  	for _, v := range s.Starts {
    78  		if u.PublicKey() != v.PublicKey {
    79  			return nil, v1.UserErrorReply{
    80  				ErrorCode:    v1.ErrorCodePublicKeyInvalid,
    81  				ErrorContext: "not active identity",
    82  			}
    83  		}
    84  	}
    85  
    86  	// Get token from start details
    87  	var token string
    88  	for _, v := range s.Starts {
    89  		switch v.Params.Type {
    90  		case v1.VoteTypeRunoff:
    91  			// This is a runoff vote. Execute the plugin command on the
    92  			// parent record.
    93  			token = v.Params.Parent
    94  		case v1.VoteTypeStandard:
    95  			// This is a standard vote. Execute the plugin command on the
    96  			// record specified in the vote params.
    97  			token = v.Params.Token
    98  		}
    99  	}
   100  
   101  	// Send plugin command
   102  	ts := convertStartToPlugin(s)
   103  	tsr, err := t.politeiad.TicketVoteStart(ctx, token, ts)
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  
   108  	// Emit notification for each start
   109  	t.events.Emit(EventTypeStart,
   110  		EventStart{
   111  			Starts: s.Starts,
   112  			User:   u,
   113  		})
   114  
   115  	return &v1.StartReply{
   116  		Receipt:          tsr.Receipt,
   117  		StartBlockHeight: tsr.StartBlockHeight,
   118  		StartBlockHash:   tsr.StartBlockHash,
   119  		EndBlockHeight:   tsr.EndBlockHeight,
   120  		EligibleTickets:  tsr.EligibleTickets,
   121  	}, nil
   122  }
   123  
   124  func (t *TicketVote) processCastBallot(ctx context.Context, cb v1.CastBallot) (*v1.CastBallotReply, error) {
   125  	log.Tracef("processCastBallot")
   126  
   127  	// Get token from one of the votes
   128  	var token string
   129  	for _, v := range cb.Votes {
   130  		token = v.Token
   131  		break
   132  	}
   133  
   134  	// Send plugin command
   135  	tcb := ticketvote.CastBallot{
   136  		Ballot: convertCastVotesToPlugin(cb.Votes),
   137  	}
   138  	tcbr, err := t.politeiad.TicketVoteCastBallot(ctx, token, tcb)
   139  	if err != nil {
   140  		return nil, err
   141  	}
   142  
   143  	return &v1.CastBallotReply{
   144  		Receipts: convertCastVoteRepliesToV1(tcbr.Receipts),
   145  	}, nil
   146  }
   147  
   148  func (t *TicketVote) processDetails(ctx context.Context, d v1.Details) (*v1.DetailsReply, error) {
   149  	log.Tracef("processsDetails: %v", d.Token)
   150  
   151  	tdr, err := t.politeiad.TicketVoteDetails(ctx, d.Token)
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  
   156  	var vote *v1.VoteDetails
   157  	if tdr.Vote != nil {
   158  		vd := convertVoteDetailsToV1(*tdr.Vote)
   159  		vote = &vd
   160  	}
   161  
   162  	return &v1.DetailsReply{
   163  		Auths: convertAuthDetailsToV1(tdr.Auths),
   164  		Vote:  vote,
   165  	}, nil
   166  }
   167  
   168  func (t *TicketVote) processResults(ctx context.Context, r v1.Results) (*v1.ResultsReply, error) {
   169  	log.Tracef("processResults: %v", r.Token)
   170  
   171  	rr, err := t.politeiad.TicketVoteResults(ctx, r.Token)
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  
   176  	return &v1.ResultsReply{
   177  		Votes: convertCastVoteDetailsToV1(rr.Votes),
   178  	}, nil
   179  }
   180  
   181  func (t *TicketVote) processSummaries(ctx context.Context, s v1.Summaries) (*v1.SummariesReply, error) {
   182  	log.Tracef("processSummaries: %v", s.Tokens)
   183  
   184  	// Verify request size
   185  	if len(s.Tokens) > int(t.policy.SummariesPageSize) {
   186  		return nil, v1.UserErrorReply{
   187  			ErrorCode: v1.ErrorCodePageSizeExceeded,
   188  			ErrorContext: fmt.Sprintf("max page size is %v",
   189  				t.policy.SummariesPageSize),
   190  		}
   191  	}
   192  
   193  	// Get vote summaries
   194  	ts, err := t.politeiad.TicketVoteSummaries(ctx, s.Tokens)
   195  	if err != nil {
   196  		return nil, err
   197  	}
   198  
   199  	return &v1.SummariesReply{
   200  		Summaries: convertSummariesToV1(ts),
   201  	}, nil
   202  }
   203  
   204  func (t *TicketVote) processSubmissions(ctx context.Context, s v1.Submissions) (*v1.SubmissionsReply, error) {
   205  	log.Tracef("processSubmissions: %v", s.Token)
   206  
   207  	subs, err := t.politeiad.TicketVoteSubmissions(ctx, s.Token)
   208  	if err != nil {
   209  		return nil, err
   210  	}
   211  
   212  	return &v1.SubmissionsReply{
   213  		Submissions: subs,
   214  	}, nil
   215  }
   216  
   217  func (t *TicketVote) processInventory(ctx context.Context, i v1.Inventory) (*v1.InventoryReply, error) {
   218  	log.Tracef("processInventory: %v %v", i.Status, i.Page)
   219  
   220  	// Get inventory
   221  	ti := ticketvote.Inventory{
   222  		Status: convertVoteStatusToPlugin(i.Status),
   223  		Page:   i.Page,
   224  	}
   225  	ir, err := t.politeiad.TicketVoteInventory(ctx, ti)
   226  	if err != nil {
   227  		return nil, err
   228  	}
   229  
   230  	return &v1.InventoryReply{
   231  		Vetted:    ir.Tokens,
   232  		BestBlock: ir.BestBlock,
   233  	}, nil
   234  }
   235  
   236  func (t *TicketVote) processTimestamps(ctx context.Context, ts v1.Timestamps) (*v1.TimestampsReply, error) {
   237  	log.Tracef("processTimestamps: %v %v", ts.Token, ts.VotesPage)
   238  
   239  	// Send plugin command
   240  	tt := ticketvote.Timestamps{
   241  		VotesPage: ts.VotesPage,
   242  	}
   243  	tsr, err := t.politeiad.TicketVoteTimestamps(ctx, ts.Token, tt)
   244  	if err != nil {
   245  		return nil, err
   246  	}
   247  
   248  	// Prepare reply
   249  	var (
   250  		auths = make([]v1.Timestamp, 0, len(tsr.Auths))
   251  		votes = make([]v1.Timestamp, 0, len(tsr.Votes))
   252  
   253  		details *v1.Timestamp
   254  	)
   255  	if tsr.Details != nil {
   256  		dt := convertTimestampToV1(*tsr.Details)
   257  		details = &dt
   258  	}
   259  	for _, v := range tsr.Auths {
   260  		auths = append(auths, convertTimestampToV1(v))
   261  	}
   262  	for _, v := range tsr.Votes {
   263  		votes = append(votes, convertTimestampToV1(v))
   264  	}
   265  
   266  	return &v1.TimestampsReply{
   267  		Auths:   auths,
   268  		Details: details,
   269  		Votes:   votes,
   270  	}, nil
   271  }
   272  
   273  func convertVoteStatusToPlugin(s v1.VoteStatusT) ticketvote.VoteStatusT {
   274  	switch s {
   275  	case v1.VoteStatusUnauthorized:
   276  		return ticketvote.VoteStatusUnauthorized
   277  	case v1.VoteStatusAuthorized:
   278  		return ticketvote.VoteStatusAuthorized
   279  	case v1.VoteStatusStarted:
   280  		return ticketvote.VoteStatusStarted
   281  	case v1.VoteStatusFinished:
   282  		return ticketvote.VoteStatusFinished
   283  	case v1.VoteStatusApproved:
   284  		return ticketvote.VoteStatusApproved
   285  	case v1.VoteStatusRejected:
   286  		return ticketvote.VoteStatusRejected
   287  	case v1.VoteStatusIneligible:
   288  		return ticketvote.VoteStatusIneligible
   289  	default:
   290  		return ticketvote.VoteStatusInvalid
   291  	}
   292  }
   293  
   294  func convertVoteTypeToPlugin(t v1.VoteT) ticketvote.VoteT {
   295  	switch t {
   296  	case v1.VoteTypeStandard:
   297  		return ticketvote.VoteTypeStandard
   298  	case v1.VoteTypeRunoff:
   299  		return ticketvote.VoteTypeRunoff
   300  	}
   301  	return ticketvote.VoteTypeInvalid
   302  }
   303  
   304  func convertVoteParamsToPlugin(v v1.VoteParams) ticketvote.VoteParams {
   305  	tv := ticketvote.VoteParams{
   306  		Token:            v.Token,
   307  		Version:          v.Version,
   308  		Type:             convertVoteTypeToPlugin(v.Type),
   309  		Mask:             v.Mask,
   310  		Duration:         v.Duration,
   311  		QuorumPercentage: v.QuorumPercentage,
   312  		PassPercentage:   v.PassPercentage,
   313  		Parent:           v.Parent,
   314  	}
   315  	// Convert vote options
   316  	vo := make([]ticketvote.VoteOption, 0, len(v.Options))
   317  	for _, vi := range v.Options {
   318  		vo = append(vo, ticketvote.VoteOption{
   319  			ID:          vi.ID,
   320  			Description: vi.Description,
   321  			Bit:         vi.Bit,
   322  		})
   323  	}
   324  	tv.Options = vo
   325  
   326  	return tv
   327  }
   328  
   329  func convertStartDetailsToPlugin(sd v1.StartDetails) ticketvote.StartDetails {
   330  	return ticketvote.StartDetails{
   331  		Params:    convertVoteParamsToPlugin(sd.Params),
   332  		PublicKey: sd.PublicKey,
   333  		Signature: sd.Signature,
   334  	}
   335  }
   336  
   337  func convertStartToPlugin(vs v1.Start) ticketvote.Start {
   338  	starts := make([]ticketvote.StartDetails, 0, len(vs.Starts))
   339  	for _, v := range vs.Starts {
   340  		starts = append(starts, convertStartDetailsToPlugin(v))
   341  	}
   342  	return ticketvote.Start{
   343  		Starts: starts,
   344  	}
   345  }
   346  
   347  func convertCastVotesToPlugin(votes []v1.CastVote) []ticketvote.CastVote {
   348  	cv := make([]ticketvote.CastVote, 0, len(votes))
   349  	for _, v := range votes {
   350  		cv = append(cv, ticketvote.CastVote{
   351  			Token:     v.Token,
   352  			Ticket:    v.Ticket,
   353  			VoteBit:   v.VoteBit,
   354  			Signature: v.Signature,
   355  		})
   356  	}
   357  	return cv
   358  }
   359  
   360  func convertVoteTypeToV1(t ticketvote.VoteT) v1.VoteT {
   361  	switch t {
   362  	case ticketvote.VoteTypeStandard:
   363  		return v1.VoteTypeStandard
   364  	case ticketvote.VoteTypeRunoff:
   365  		return v1.VoteTypeRunoff
   366  	}
   367  	return v1.VoteTypeInvalid
   368  
   369  }
   370  
   371  func convertVoteParamsToV1(v ticketvote.VoteParams) v1.VoteParams {
   372  	vp := v1.VoteParams{
   373  		Token:            v.Token,
   374  		Version:          v.Version,
   375  		Type:             convertVoteTypeToV1(v.Type),
   376  		Mask:             v.Mask,
   377  		Duration:         v.Duration,
   378  		QuorumPercentage: v.QuorumPercentage,
   379  		PassPercentage:   v.PassPercentage,
   380  	}
   381  	vo := make([]v1.VoteOption, 0, len(v.Options))
   382  	for _, o := range v.Options {
   383  		vo = append(vo, v1.VoteOption{
   384  			ID:          o.ID,
   385  			Description: o.Description,
   386  			Bit:         o.Bit,
   387  		})
   388  	}
   389  	vp.Options = vo
   390  
   391  	return vp
   392  }
   393  
   394  func convertVoteErrorToV1(e *ticketvote.VoteErrorT) *v1.VoteErrorT {
   395  	if e == nil {
   396  		return nil
   397  	}
   398  
   399  	var ve v1.VoteErrorT
   400  	switch *e {
   401  	case ticketvote.VoteErrorInvalid:
   402  		ve = v1.VoteErrorInvalid
   403  	case ticketvote.VoteErrorInternalError:
   404  		ve = v1.VoteErrorInternalError
   405  	case ticketvote.VoteErrorRecordNotFound:
   406  		ve = v1.VoteErrorRecordNotFound
   407  	case ticketvote.VoteErrorVoteBitInvalid:
   408  		ve = v1.VoteErrorVoteBitInvalid
   409  	case ticketvote.VoteErrorVoteStatusInvalid:
   410  		ve = v1.VoteErrorVoteStatusInvalid
   411  	case ticketvote.VoteErrorTicketAlreadyVoted:
   412  		ve = v1.VoteErrorTicketAlreadyVoted
   413  	case ticketvote.VoteErrorTicketNotEligible:
   414  		ve = v1.VoteErrorTicketNotEligible
   415  	default:
   416  		ve = v1.VoteErrorInternalError
   417  	}
   418  
   419  	return &ve
   420  }
   421  
   422  func convertCastVoteRepliesToV1(replies []ticketvote.CastVoteReply) []v1.CastVoteReply {
   423  	r := make([]v1.CastVoteReply, 0, len(replies))
   424  	for _, v := range replies {
   425  		r = append(r, v1.CastVoteReply{
   426  			Ticket:       v.Ticket,
   427  			Receipt:      v.Receipt,
   428  			ErrorCode:    convertVoteErrorToV1(v.ErrorCode),
   429  			ErrorContext: v.ErrorContext,
   430  		})
   431  	}
   432  	return r
   433  }
   434  
   435  func convertVoteDetailsToV1(vd ticketvote.VoteDetails) v1.VoteDetails {
   436  	return v1.VoteDetails{
   437  		Params:           convertVoteParamsToV1(vd.Params),
   438  		PublicKey:        vd.PublicKey,
   439  		Signature:        vd.Signature,
   440  		Receipt:          vd.Receipt,
   441  		StartBlockHeight: vd.StartBlockHeight,
   442  		StartBlockHash:   vd.StartBlockHash,
   443  		EndBlockHeight:   vd.EndBlockHeight,
   444  		EligibleTickets:  vd.EligibleTickets,
   445  	}
   446  }
   447  
   448  func convertAuthDetailsToV1(auths []ticketvote.AuthDetails) []v1.AuthDetails {
   449  	a := make([]v1.AuthDetails, 0, len(auths))
   450  	for _, v := range auths {
   451  		a = append(a, v1.AuthDetails{
   452  			Token:     v.Token,
   453  			Version:   v.Version,
   454  			Action:    v.Action,
   455  			PublicKey: v.PublicKey,
   456  			Signature: v.Signature,
   457  			Timestamp: v.Timestamp,
   458  			Receipt:   v.Receipt,
   459  		})
   460  	}
   461  	return a
   462  }
   463  
   464  func convertCastVoteDetailsToV1(votes []ticketvote.CastVoteDetails) []v1.CastVoteDetails {
   465  	vs := make([]v1.CastVoteDetails, 0, len(votes))
   466  	for _, v := range votes {
   467  		vs = append(vs, v1.CastVoteDetails{
   468  			Token:     v.Token,
   469  			Ticket:    v.Ticket,
   470  			VoteBit:   v.VoteBit,
   471  			Address:   v.Address,
   472  			Signature: v.Signature,
   473  			Receipt:   v.Receipt,
   474  			Timestamp: v.Timestamp,
   475  		})
   476  	}
   477  	return vs
   478  }
   479  
   480  func convertVoteStatusToV1(s ticketvote.VoteStatusT) v1.VoteStatusT {
   481  	switch s {
   482  	case ticketvote.VoteStatusInvalid:
   483  		return v1.VoteStatusInvalid
   484  	case ticketvote.VoteStatusUnauthorized:
   485  		return v1.VoteStatusUnauthorized
   486  	case ticketvote.VoteStatusAuthorized:
   487  		return v1.VoteStatusAuthorized
   488  	case ticketvote.VoteStatusStarted:
   489  		return v1.VoteStatusStarted
   490  	case ticketvote.VoteStatusFinished:
   491  		return v1.VoteStatusFinished
   492  	case ticketvote.VoteStatusApproved:
   493  		return v1.VoteStatusApproved
   494  	case ticketvote.VoteStatusRejected:
   495  		return v1.VoteStatusRejected
   496  	case ticketvote.VoteStatusIneligible:
   497  		return v1.VoteStatusIneligible
   498  	default:
   499  		return v1.VoteStatusInvalid
   500  	}
   501  }
   502  
   503  func convertSummaryToV1(s ticketvote.SummaryReply) v1.Summary {
   504  	results := make([]v1.VoteResult, 0, len(s.Results))
   505  	for _, v := range s.Results {
   506  		results = append(results, v1.VoteResult{
   507  			ID:          v.ID,
   508  			Description: v.Description,
   509  			VoteBit:     v.VoteBit,
   510  			Votes:       v.Votes,
   511  		})
   512  	}
   513  	return v1.Summary{
   514  		Type:             convertVoteTypeToV1(s.Type),
   515  		Status:           convertVoteStatusToV1(s.Status),
   516  		Duration:         s.Duration,
   517  		StartBlockHeight: s.StartBlockHeight,
   518  		StartBlockHash:   s.StartBlockHash,
   519  		EndBlockHeight:   s.EndBlockHeight,
   520  		EligibleTickets:  s.EligibleTickets,
   521  		QuorumPercentage: s.QuorumPercentage,
   522  		PassPercentage:   s.PassPercentage,
   523  		Results:          results,
   524  		BestBlock:        s.BestBlock,
   525  	}
   526  }
   527  
   528  func convertSummariesToV1(s map[string]ticketvote.SummaryReply) map[string]v1.Summary {
   529  	ts := make(map[string]v1.Summary, len(s))
   530  	for k, v := range s {
   531  		ts[k] = convertSummaryToV1(v)
   532  	}
   533  	return ts
   534  }
   535  
   536  func convertProofToV1(p ticketvote.Proof) v1.Proof {
   537  	return v1.Proof{
   538  		Type:       p.Type,
   539  		Digest:     p.Digest,
   540  		MerkleRoot: p.MerkleRoot,
   541  		MerklePath: p.MerklePath,
   542  		ExtraData:  p.ExtraData,
   543  	}
   544  }
   545  
   546  func convertTimestampToV1(t ticketvote.Timestamp) v1.Timestamp {
   547  	proofs := make([]v1.Proof, 0, len(t.Proofs))
   548  	for _, v := range t.Proofs {
   549  		proofs = append(proofs, convertProofToV1(v))
   550  	}
   551  	return v1.Timestamp{
   552  		Data:       t.Data,
   553  		Digest:     t.Digest,
   554  		TxID:       t.TxID,
   555  		MerkleRoot: t.MerkleRoot,
   556  		Proofs:     proofs,
   557  	}
   558  }