github.com/decred/politeia@v1.4.0/politeiawww/cmd/pictl/cmdvotestart.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 main
     6  
     7  import (
     8  	"encoding/hex"
     9  	"encoding/json"
    10  	"fmt"
    11  
    12  	"github.com/decred/politeia/politeiad/plugins/ticketvote"
    13  	rcv1 "github.com/decred/politeia/politeiawww/api/records/v1"
    14  	tkv1 "github.com/decred/politeia/politeiawww/api/ticketvote/v1"
    15  	pclient "github.com/decred/politeia/politeiawww/client"
    16  	"github.com/decred/politeia/politeiawww/cmd/shared"
    17  	"github.com/decred/politeia/util"
    18  )
    19  
    20  // cmdVoteStart starts the voting period on a record.
    21  type cmdVoteStart struct {
    22  	Args struct {
    23  		Token string `positional-arg-name:"token"`
    24  	} `positional-args:"true"  required:"true"`
    25  
    26  	// Duration is the duration, in blocks of the DCR ticket vote.
    27  	Duration uint32 `long:"duration"`
    28  
    29  	// Quorum is the percent of total votes required for a quorum. This is a
    30  	// pointer so that a value of 0 can be provided. A quorum of zero allows
    31  	// for the vote to be approved or rejected using a single DCR ticket.
    32  	Quorum *uint32 `long:"quorum"`
    33  
    34  	// Passing is the percent of cast votes required for a vote options to be
    35  	// considered as passing.
    36  	Passing uint32 `long:"passing"`
    37  
    38  	// Runoff is used to indicate the vote is a runoff vote and the
    39  	// provided token is the parent token of the runoff vote.
    40  	Runoff bool `long:"runoff"`
    41  }
    42  
    43  // Execute executes the cmdVoteStart command.
    44  //
    45  // This function satisfies the go-flags Commander interface.
    46  func (c *cmdVoteStart) Execute(args []string) error {
    47  	token := c.Args.Token
    48  
    49  	// Verify user identity. An identity is required to sign the vote
    50  	// start.
    51  	if cfg.Identity == nil {
    52  		return shared.ErrUserIdentityNotFound
    53  	}
    54  
    55  	// Setup the vote params. The default values
    56  	// are overridden if CLI flags are provided.
    57  	var (
    58  		duration = defaultDuration
    59  		quorum   = defaultQuorum
    60  		passing  = defaultPassing
    61  	)
    62  	if c.Duration > 0 {
    63  		duration = c.Duration
    64  	}
    65  	if c.Quorum != nil {
    66  		quorum = *c.Quorum
    67  	}
    68  	if c.Passing != 0 {
    69  		passing = c.Passing
    70  	}
    71  
    72  	// Setup client
    73  	opts := pclient.Opts{
    74  		HTTPSCert:  cfg.HTTPSCert,
    75  		Cookies:    cfg.Cookies,
    76  		HeaderCSRF: cfg.CSRF,
    77  		Verbose:    cfg.Verbose,
    78  		RawJSON:    cfg.RawJSON,
    79  	}
    80  	pc, err := pclient.New(cfg.Host, opts)
    81  	if err != nil {
    82  		return err
    83  	}
    84  
    85  	// Start the voting period
    86  	var sr *tkv1.StartReply
    87  	if c.Runoff {
    88  		sr, err = voteStartRunoff(token, duration, quorum, passing, pc)
    89  		if err != nil {
    90  			return err
    91  		}
    92  	} else {
    93  		sr, err = voteStartStandard(token, duration, quorum, passing, pc)
    94  		if err != nil {
    95  			return err
    96  		}
    97  	}
    98  
    99  	// Print reply
   100  	printf("Receipt         : %v\n", sr.Receipt)
   101  	printf("StartBlockHash  : %v\n", sr.StartBlockHash)
   102  	printf("StartBlockHeight: %v\n", sr.StartBlockHeight)
   103  	printf("EndBlockHeight  : %v\n", sr.EndBlockHeight)
   104  
   105  	return nil
   106  }
   107  
   108  func voteStartStandard(token string, duration, quorum, pass uint32, pc *pclient.Client) (*tkv1.StartReply, error) {
   109  	// Get record version
   110  	d := rcv1.Details{
   111  		Token: token,
   112  	}
   113  	r, err := pc.RecordDetails(d)
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  
   118  	// Setup request
   119  	vp := tkv1.VoteParams{
   120  		Token:            token,
   121  		Version:          r.Version,
   122  		Type:             tkv1.VoteTypeStandard,
   123  		Mask:             0x03,
   124  		Duration:         duration,
   125  		QuorumPercentage: quorum,
   126  		PassPercentage:   pass,
   127  		Options: []tkv1.VoteOption{
   128  			{
   129  				ID:          tkv1.VoteOptionIDApprove,
   130  				Description: "Approve the proposal",
   131  				Bit:         0x01,
   132  			},
   133  			{
   134  				ID:          tkv1.VoteOptionIDReject,
   135  				Description: "Reject the proposal",
   136  				Bit:         0x02,
   137  			},
   138  		},
   139  	}
   140  	vpb, err := json.Marshal(vp)
   141  	if err != nil {
   142  		return nil, err
   143  	}
   144  	msg := hex.EncodeToString(util.Digest(vpb))
   145  	b := cfg.Identity.SignMessage([]byte(msg))
   146  	signature := hex.EncodeToString(b[:])
   147  	s := tkv1.Start{
   148  		Starts: []tkv1.StartDetails{
   149  			{
   150  				Params:    vp,
   151  				PublicKey: cfg.Identity.Public.String(),
   152  				Signature: signature,
   153  			},
   154  		},
   155  	}
   156  
   157  	// Send request
   158  	return pc.TicketVoteStart(s)
   159  }
   160  
   161  func voteStartRunoff(parentToken string, duration, quorum, pass uint32, pc *pclient.Client) (*tkv1.StartReply, error) {
   162  	// Get runoff vote submissions
   163  	s := tkv1.Submissions{
   164  		Token: parentToken,
   165  	}
   166  	sr, err := pc.TicketVoteSubmissions(s)
   167  	if err != nil {
   168  		return nil, fmt.Errorf("TicketVoteSubmissions: %v", err)
   169  	}
   170  
   171  	// Prepare start details for each submission
   172  	starts := make([]tkv1.StartDetails, 0, len(sr.Submissions))
   173  	for _, v := range sr.Submissions {
   174  		// Get record
   175  		d := rcv1.Details{
   176  			Token: v,
   177  		}
   178  		r, err := pc.RecordDetails(d)
   179  		if err != nil {
   180  			return nil, fmt.Errorf("RecordDetails %v: %v", v, err)
   181  		}
   182  
   183  		// Don't include the record if it has been abandoned.
   184  		if r.Status == rcv1.RecordStatusArchived {
   185  			continue
   186  		}
   187  
   188  		// Setup vote params
   189  		vp := tkv1.VoteParams{
   190  			Token:            r.CensorshipRecord.Token,
   191  			Version:          r.Version,
   192  			Type:             tkv1.VoteTypeRunoff,
   193  			Mask:             0x03, // bit 0 no, bit 1 yes
   194  			Duration:         duration,
   195  			QuorumPercentage: quorum,
   196  			PassPercentage:   pass,
   197  			Options: []tkv1.VoteOption{
   198  				{
   199  					ID:          ticketvote.VoteOptionIDApprove,
   200  					Description: "Approve the proposal",
   201  					Bit:         0x01,
   202  				},
   203  				{
   204  					ID:          ticketvote.VoteOptionIDReject,
   205  					Description: "Reject the proposal",
   206  					Bit:         0x02,
   207  				},
   208  			},
   209  			Parent: parentToken,
   210  		}
   211  		vpb, err := json.Marshal(vp)
   212  		if err != nil {
   213  			return nil, err
   214  		}
   215  		msg := hex.EncodeToString(util.Digest(vpb))
   216  		sig := cfg.Identity.SignMessage([]byte(msg))
   217  		starts = append(starts, tkv1.StartDetails{
   218  			Params:    vp,
   219  			PublicKey: cfg.Identity.Public.String(),
   220  			Signature: hex.EncodeToString(sig[:]),
   221  		})
   222  	}
   223  
   224  	// Send request
   225  	ts := tkv1.Start{
   226  		Starts: starts,
   227  	}
   228  	return pc.TicketVoteStart(ts)
   229  }
   230  
   231  // voteStartHelpMsg is printed to stdout by the help command.
   232  var voteStartHelpMsg = `votestart <token>
   233  
   234  Start a DCR ticket vote for a record. Requires admin privileges.
   235  
   236  If the vote is a runoff vote then the --runoff flag must be used. The provided
   237  token should be the parent token of the runoff vote.
   238  
   239  Arguments:
   240  1. token (string, required) Record censorship token.
   241  
   242  Flags:
   243   --duration (uint32) Duration, in blocks, of the vote.
   244                       (default: 6)
   245   --quorum   (uint32) Percent of total votes required to reach a quorum. A
   246                       quorum of 0 means that the vote can be approved or
   247                       rejected using a single DCR ticket.
   248                       (default: 0)
   249   --passing  (uint32) Percent of cast votes required for a vote option to be
   250                       considered as passing.
   251                       (default: 60)
   252   --runoff  (bool)    The vote being started is a runoff vote.
   253                       (default: false)
   254  `