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 `