github.com/decred/politeia@v1.4.0/politeiawww/cmd/politeiavoter/trickle.go (about) 1 package main 2 3 import ( 4 "context" 5 "encoding/hex" 6 "errors" 7 "fmt" 8 "math/big" 9 "time" 10 11 "crypto/rand" 12 13 pb "decred.org/dcrwallet/rpc/walletrpc" 14 "github.com/decred/dcrd/chaincfg/chainhash" 15 tkv1 "github.com/decred/politeia/politeiawww/api/ticketvote/v1" 16 "github.com/decred/politeia/util" 17 "golang.org/x/sync/errgroup" 18 ) 19 20 // WaitUntil will block until the given time. Can be cancelled by cancelling 21 // the context 22 func WaitUntil(ctx context.Context, t time.Time) error { 23 // This garbage is a fucking retarded lint idea. 24 // We therefore replace the readable `diff := t.Sub(time.Now())` line 25 // into unreadable time.Until() crap. 26 diff := time.Until(t) 27 if diff <= 0 { 28 return nil 29 } 30 31 return WaitFor(ctx, diff) 32 } 33 34 // WaitFor will block for the specified duration or the context is cancelled 35 func WaitFor(ctx context.Context, diff time.Duration) error { 36 timer := time.NewTimer(diff) 37 defer timer.Stop() 38 39 select { 40 case <-timer.C: 41 return nil 42 case <-ctx.Done(): 43 return ctx.Err() 44 } 45 } 46 47 // voteAlarm represents a vote and the time at which it will be initially 48 // submitted to politeia. 49 type voteAlarm struct { 50 Vote tkv1.CastVote `json:"vote"` // RPC vote 51 At time.Time `json:"at"` // When initial vote will be submitted 52 } 53 54 func (p *piv) generateVoteAlarm(token, voteBit string, ctres *pb.CommittedTicketsResponse, smr *pb.SignMessagesResponse) ([]*voteAlarm, error) { 55 // Assert arrays are same length. 56 if len(ctres.TicketAddresses) != len(smr.Replies) { 57 return nil, fmt.Errorf("assert len(TicketAddresses) != "+ 58 "len(Replies) -- %v != %v", len(ctres.TicketAddresses), 59 len(smr.Replies)) 60 } 61 62 bunches := int(p.cfg.Bunches) 63 voteDuration := p.cfg.voteDuration 64 fmt.Printf("Total number of votes : %v\n", len(ctres.TicketAddresses)) 65 fmt.Printf("Total number of bunches: %v\n", bunches) 66 fmt.Printf("Vote duration : %v\n", voteDuration) 67 68 // Initialize bunches 69 tStart := make([]time.Time, bunches) 70 tEnd := make([]time.Time, bunches) 71 for i := 0; i < bunches; i++ { 72 var err error 73 tStart[i], tEnd[i], err = randomTime(voteDuration) 74 if err != nil { 75 return nil, err 76 } 77 fmt.Printf("bunchID: %v start %v end %v duration %v\n", 78 i, tStart[i], tEnd[i], tEnd[i].Sub(tStart[i])) 79 } 80 81 va := make([]*voteAlarm, len(ctres.TicketAddresses)) 82 for k := range ctres.TicketAddresses { 83 x := k % bunches 84 start := new(big.Int).SetInt64(tStart[x].Unix()) 85 end := new(big.Int).SetInt64(tEnd[x].Unix()) 86 // Generate random time to fire off vote 87 r, err := rand.Int(rand.Reader, new(big.Int).Sub(end, start)) 88 if err != nil { 89 return nil, err 90 } 91 //fmt.Printf("r : %v\n", r) 92 t := time.Unix(tStart[x].Unix()+r.Int64(), 0) 93 //fmt.Printf("at time : %v\n", t) 94 95 // Assemble missing vote bits 96 h, err := chainhash.NewHash(ctres.TicketAddresses[k].Ticket) 97 if err != nil { 98 return nil, err 99 } 100 signature := hex.EncodeToString(smr.Replies[k].Signature) 101 va[k] = &voteAlarm{ 102 Vote: tkv1.CastVote{ 103 Token: token, 104 Ticket: h.String(), 105 VoteBit: voteBit, 106 Signature: signature, 107 }, 108 At: t, 109 } 110 } 111 112 return va, nil 113 } 114 115 // randomDuration returns a randomly selected Duration between the provided 116 // min and max (in seconds). 117 func randomDuration(min, max byte) time.Duration { 118 var ( 119 wait []byte 120 err error 121 ) 122 for { 123 wait, err = util.Random(1) 124 if err != nil { 125 // This really shouldn't happen so just use min seconds 126 wait = []byte{min} 127 } else { 128 if wait[0] < min || wait[0] > max { 129 continue 130 } 131 //fmt.Printf("min %v max %v got %v\n", min, max, wait[0]) 132 } 133 break 134 } 135 return time.Duration(wait[0]) * time.Second 136 } 137 138 func (p *piv) voteTicket(ectx context.Context, bunchID, voteID, of int, va voteAlarm) error { 139 voteID++ // make human readable 140 141 // Wait 142 err := WaitUntil(ectx, va.At) 143 if err != nil { 144 return fmt.Errorf("%v bunch %v vote %v failed: %v", 145 time.Now(), bunchID, voteID, err) 146 } 147 148 // Vote 149 for retry := 0; ; retry++ { 150 var rmsg string 151 if retry != 0 { 152 // Wait between 1 and 17 seconds 153 d := randomDuration(3, 17) 154 rmsg = fmt.Sprintf("retry %v (%v) ", retry, d) 155 err = WaitFor(ectx, d) 156 if err != nil { 157 return fmt.Errorf("%v bunch %v vote %v failed: %v", 158 time.Now(), bunchID, voteID, err) 159 } 160 } 161 162 fmt.Printf("%v voting bunch %v vote %v %v%v\n", 163 time.Now(), bunchID, voteID, rmsg, va.Vote.Ticket) 164 165 // Send off vote 166 b := tkv1.CastBallot{Votes: []tkv1.CastVote{va.Vote}} 167 vr, err := p.sendVote(&b) 168 var e ErrRetry 169 if errors.As(err, &e) { 170 // Append failed vote to retry queue 171 fmt.Printf("Vote rescheduled: %v\n", va.Vote.Ticket) 172 err := p.jsonLog(failedJournal, va.Vote.Token, b, e) 173 if err != nil { 174 return fmt.Errorf("0 jsonLog: %v", err) 175 } 176 177 // Retry 178 continue 179 180 } else if err != nil { 181 // Unrecoverable error 182 return fmt.Errorf("unrecoverable error: %v", 183 err) 184 } 185 186 // Evaluate errors when ErrorCode is set 187 if vr.ErrorCode != nil { 188 switch *vr.ErrorCode { 189 // Silently ignore. 190 case tkv1.VoteErrorTicketAlreadyVoted: 191 // This happens during network errors. Since 192 // the ticket has already voted record success 193 // and exit. 194 195 // Restart 196 case tkv1.VoteErrorInternalError: 197 // Politeia puked. Retry later to see if it 198 // recovered. 199 continue 200 201 // Non-terminal errors 202 case tkv1.VoteErrorTokenInvalid, 203 tkv1.VoteErrorRecordNotFound, 204 tkv1.VoteErrorMultipleRecordVotes, 205 tkv1.VoteErrorVoteBitInvalid, 206 tkv1.VoteErrorSignatureInvalid, 207 tkv1.VoteErrorTicketNotEligible: 208 209 // Log failure 210 err = p.jsonLog(failedJournal, va.Vote.Token, vr) 211 if err != nil { 212 return fmt.Errorf("1 jsonLog: %v", err) 213 } 214 215 // We have to do this for all failures, this 216 // should be rewritten. 217 p.Lock() 218 p.ballotResults = append(p.ballotResults, *vr) 219 p.Unlock() 220 221 return nil 222 223 // Terminal 224 case tkv1.VoteErrorVoteStatusInvalid: 225 // Force an exit of the both the main queue and the 226 // retry queue if the voting period has ended. 227 err = p.jsonLog(failedJournal, va.Vote.Token, vr) 228 if err != nil { 229 return fmt.Errorf("2 jsonLog: %v", err) 230 } 231 return fmt.Errorf("Vote has ended; forced " + 232 "exit main vote queue.") 233 234 // Should not happen 235 default: 236 // Log failure 237 err = p.jsonLog(failedJournal, va.Vote.Token, vr) 238 if err != nil { 239 return fmt.Errorf("3 jsonLog: %v", err) 240 } 241 242 // We have to do this for all failures, this 243 // should be rewritten. 244 p.Lock() 245 p.ballotResults = append(p.ballotResults, *vr) 246 p.Unlock() 247 248 return nil 249 } 250 } 251 252 // Success, log it and exit 253 err = p.jsonLog(successJournal, va.Vote.Token, vr) 254 if err != nil { 255 return fmt.Errorf("3 jsonLog: %v", err) 256 } 257 258 // All done with this vote 259 // Vote completed 260 p.Lock() 261 p.ballotResults = append(p.ballotResults, *vr) 262 263 // This is required to be in the lock to prevent a 264 // ballotResults race 265 fmt.Printf("%v finished bunch %v vote %v -- "+ 266 "total progress %v/%v\n", time.Now(), bunchID, 267 voteID, len(p.ballotResults), cap(p.ballotResults)) 268 p.Unlock() 269 270 return nil 271 } 272 273 // Not reached 274 } 275 276 func randomInt64(min, max int64) (int64, error) { 277 mi := new(big.Int).SetInt64(min) 278 ma := new(big.Int).SetInt64(max) 279 r, err := rand.Int(rand.Reader, new(big.Int).Sub(ma, mi)) 280 if err != nil { 281 return 0, err 282 } 283 return new(big.Int).Add(mi, r).Int64(), nil 284 } 285 286 func randomTime(d time.Duration) (time.Time, time.Time, error) { 287 now := time.Now() 288 halfDuration := int64(d / 2) 289 st, err := randomInt64(0, halfDuration*90/100) // up to 90% of half 290 if err != nil { 291 return time.Time{}, time.Time{}, err 292 } 293 et, err := randomInt64(halfDuration, int64(d)) 294 if err != nil { 295 return time.Time{}, time.Time{}, err 296 } 297 startTime := now.Add(time.Duration(st)).Unix() 298 endTime := now.Add(time.Duration(et)).Unix() 299 return time.Unix(startTime, 0), time.Unix(endTime, 0), nil 300 } 301 302 func (p *piv) alarmTrickler(token, voteBit string, ctres *pb.CommittedTicketsResponse, smr *pb.SignMessagesResponse) error { 303 // Generate work queue 304 votes, err := p.generateVoteAlarm(token, voteBit, ctres, smr) 305 if err != nil { 306 return err 307 } 308 309 // Log work 310 err = p.jsonLog(workJournal, token, votes) 311 if err != nil { 312 return err 313 } 314 315 // Launch the voting stats handler 316 go p.statsHandler() 317 318 // Launch voting go routines 319 eg, ectx := errgroup.WithContext(p.ctx) 320 p.ballotResults = make([]tkv1.CastVoteReply, 0, len(ctres.TicketAddresses)) 321 div := len(votes) / int(p.cfg.Bunches) 322 mod := len(votes) % int(p.cfg.Bunches) 323 for k := range votes { 324 voterID := k 325 bunchID := voterID % int(p.cfg.Bunches) 326 v := *votes[k] 327 328 // Calculate of 329 of := div 330 if mod != 0 && bunchID == int(p.cfg.Bunches)-1 { 331 of = mod 332 } 333 eg.Go(func() error { 334 return p.voteTicket(ectx, bunchID, voterID, of, v) 335 }) 336 } 337 err = eg.Wait() 338 if err != nil { 339 //fmt.Printf("%v\n", err) 340 return err 341 } 342 343 return nil 344 }