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  }