-
Notifications
You must be signed in to change notification settings - Fork 75
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Make trickler truly random instead of using random bucketized duratio…
…ns (#1368) * Make trickler truly random instead of using random bucketized durations * Remove comments now that the code has been validated
- Loading branch information
1 parent
9019d9d
commit 51a60c4
Showing
5 changed files
with
246 additions
and
111 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/hex" | ||
"fmt" | ||
"sort" | ||
"time" | ||
|
||
"crypto/rand" | ||
|
||
pb "decred.org/dcrwallet/rpc/walletrpc" | ||
"github.com/decred/dcrd/chaincfg/chainhash" | ||
v1 "github.com/decred/politeia/politeiawww/api/www/v1" | ||
"github.com/decred/politeia/politeiawww/cmd/politeiavoter/uniformprng" | ||
) | ||
|
||
func (c *ctx) calculateTrickle(token, voteBit string, ctres *pb.CommittedTicketsResponse, smr *pb.SignMessagesResponse) error { | ||
votes := len(ctres.TicketAddresses) | ||
duration := c.cfg.voteDuration | ||
voteDuration := duration - time.Hour | ||
if voteDuration < time.Hour { | ||
return fmt.Errorf("not enough time left to trickle votes") | ||
} | ||
fmt.Printf("Total number of votes: %v\n", votes) | ||
fmt.Printf("Total vote duration : %v\n", duration) | ||
fmt.Printf("Duration calculated : %v\n", voteDuration) | ||
|
||
prng, err := uniformprng.RandSource(rand.Reader) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
ts := make([]time.Duration, 0, votes) | ||
for i := 0; i < votes; i++ { | ||
ts = append(ts, time.Duration(prng.Int63n(int64(voteDuration)))) | ||
} | ||
sort.Slice(ts, func(i, j int) bool { return ts[i] < ts[j] }) | ||
var previous, t time.Duration | ||
|
||
buckets := make([]*voteInterval, votes) | ||
for k := range ts { | ||
// Assemble missing vote bits | ||
h, err := chainhash.NewHash(ctres.TicketAddresses[k].Ticket) | ||
if err != nil { | ||
return err | ||
} | ||
signature := hex.EncodeToString(smr.Replies[k].Signature) | ||
|
||
buckets[k] = &voteInterval{ | ||
Vote: v1.CastVote{ | ||
Token: token, | ||
Ticket: h.String(), | ||
VoteBit: voteBit, | ||
Signature: signature, | ||
}, | ||
At: ts[k] - previous, // Delta to previous timestamp | ||
} | ||
t += ts[k] - previous | ||
previous = ts[k] | ||
} | ||
|
||
// Should not happen | ||
if t > voteDuration { | ||
return fmt.Errorf("assert t > voteDuration - %v > %v", | ||
t, voteDuration) | ||
} | ||
|
||
// Sanity | ||
if len(buckets) != len(ctres.TicketAddresses) { | ||
return fmt.Errorf("unexpected time bucket count got "+ | ||
"%v, wanted %v", len(ctres.TicketAddresses), | ||
len(buckets)) | ||
} | ||
|
||
// Convert buckets to a list | ||
for _, v := range buckets { | ||
c.voteIntervalPush(v) | ||
} | ||
|
||
// Log work | ||
err = c.jsonLog(workJournal, token, buckets) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package main | ||
|
||
import ( | ||
"container/list" | ||
"testing" | ||
"time" | ||
|
||
pb "decred.org/dcrwallet/rpc/walletrpc" | ||
) | ||
|
||
func fakeTickets(x int) (*pb.CommittedTicketsResponse, *pb.SignMessagesResponse) { | ||
ctres := pb.CommittedTicketsResponse{ | ||
TicketAddresses: make([]*pb.CommittedTicketsResponse_TicketAddress, x), | ||
} | ||
for k := range ctres.TicketAddresses { | ||
ctres.TicketAddresses[k] = &pb.CommittedTicketsResponse_TicketAddress{ | ||
Ticket: make([]byte, 32), | ||
} | ||
} | ||
smr := pb.SignMessagesResponse{ | ||
Replies: make([]*pb.SignMessagesResponse_SignReply, x), | ||
} | ||
for k := range smr.Replies { | ||
smr.Replies[k] = &pb.SignMessagesResponse_SignReply{ | ||
Signature: make([]byte, 64), | ||
} | ||
} | ||
|
||
return &ctres, &smr | ||
} | ||
|
||
func fakeCtx(d time.Duration, x int) *ctx { | ||
return &ctx{ | ||
cfg: &config{ | ||
voteDuration: d, | ||
}, | ||
voteIntervalQ: new(list.List), | ||
} | ||
} | ||
|
||
func TestTrickleNotEnoughTime(t *testing.T) { | ||
x := 10 | ||
c := fakeCtx(time.Hour, x) | ||
ctres, smr := fakeTickets(x) | ||
err := c.calculateTrickle("", "", ctres, smr) | ||
if err == nil { | ||
t.Fatal("expected error") | ||
} | ||
} | ||
|
||
func TestTrickle2(t *testing.T) { | ||
x := 10 | ||
c := fakeCtx(24*time.Hour, x) | ||
ctres, smr := fakeTickets(x) | ||
err := c.calculateTrickle("", "", ctres, smr) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
// Package uniformprng implements a uniform, cryptographically secure | ||
// pseudo-random number generator. | ||
package uniformprng | ||
|
||
import ( | ||
"encoding/binary" | ||
"io" | ||
"math/bits" | ||
|
||
"golang.org/x/crypto/chacha20" | ||
) | ||
|
||
// Source returns cryptographically-secure pseudorandom numbers with uniform | ||
// distribution. | ||
type Source struct { | ||
buf [8]byte | ||
cipher *chacha20.Cipher | ||
} | ||
|
||
var nonce = make([]byte, chacha20.NonceSize) | ||
|
||
// NewSource seeds a Source from a 32-byte key. | ||
func NewSource(seed *[32]byte) *Source { | ||
cipher, _ := chacha20.NewUnauthenticatedCipher(seed[:], nonce) | ||
return &Source{cipher: cipher} | ||
} | ||
|
||
// RandSource creates a Source with seed randomness read from rand. | ||
func RandSource(rand io.Reader) (*Source, error) { | ||
seed := new([32]byte) | ||
_, err := io.ReadFull(rand, seed[:]) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return NewSource(seed), nil | ||
} | ||
|
||
// Uint32 returns a pseudo-random uint32. | ||
func (s *Source) Uint32() uint32 { | ||
b := s.buf[:4] | ||
for i := range b { | ||
b[i] = 0 | ||
} | ||
s.cipher.XORKeyStream(b, b) | ||
return binary.LittleEndian.Uint32(b) | ||
} | ||
|
||
// Uint32n returns a pseudo-random uint32 in range [0,n) without modulo bias. | ||
func (s *Source) Uint32n(n uint32) uint32 { | ||
if n < 2 { | ||
return 0 | ||
} | ||
n-- | ||
mask := ^uint32(0) >> bits.LeadingZeros32(n) | ||
for { | ||
u := s.Uint32() & mask | ||
if u <= n { | ||
return u | ||
} | ||
} | ||
} | ||
|
||
// Int63 returns a pseudo-random 63-bit positive integer as an int64 without | ||
// modulo bias. | ||
func (s *Source) Int63() int64 { | ||
b := s.buf[:] | ||
for i := range b { | ||
b[i] = 0 | ||
} | ||
s.cipher.XORKeyStream(b, b) | ||
return int64(binary.LittleEndian.Uint64(b) &^ (1 << 63)) | ||
} | ||
|
||
// Int63n returns, as an int64, a pseudo-random 63-bit positive integer in [0,n) | ||
// without modulo bias. | ||
// It panics if n <= 0. | ||
func (s *Source) Int63n(n int64) int64 { | ||
if n <= 0 { | ||
panic("invalid argument to Int63n") | ||
} | ||
n-- | ||
mask := int64(^uint64(0) >> bits.LeadingZeros64(uint64(n))) | ||
for { | ||
i := s.Int63() & mask | ||
if i <= n { | ||
return i | ||
} | ||
} | ||
} |