vspd/database/ticket_test.go
Jamie Holdstock c1ba46347e
Be more specific about consensus vote choices. (#278)
* Be more specific about consensus vote choices.

Rather than referring to just "Vote choices", refer specifically to "consensus vote choices".

This will be useful when treasury and tspend vote choices are added, and the distinction becomes more important.

* dnldd suggestion.
2021-07-05 09:16:08 -05:00

335 lines
9.0 KiB
Go

// Copyright (c) 2020-2021 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package database
import (
"math/rand"
"reflect"
"testing"
"time"
bolt "go.etcd.io/bbolt"
)
var seededRand = rand.New(rand.NewSource(time.Now().UnixNano()))
// randString randomly generates a string of the requested length, using only
// characters from the provided charset.
func randString(length int, charset string) string {
b := make([]byte, length)
for i := range b {
b[i] = charset[seededRand.Intn(len(charset))]
}
return string(b)
}
func exampleTicket() Ticket {
const hexCharset = "1234567890abcdef"
const addrCharset = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
return Ticket{
Hash: randString(64, hexCharset),
CommitmentAddress: randString(35, addrCharset),
FeeAddressIndex: 12345,
FeeAddress: randString(35, addrCharset),
FeeAmount: 10000000,
FeeExpiration: 4,
Confirmed: false,
VoteChoices: map[string]string{"AgendaID": "yes"},
VotingWIF: randString(53, addrCharset),
FeeTxHex: randString(504, hexCharset),
FeeTxHash: randString(64, hexCharset),
FeeTxStatus: FeeBroadcast,
}
}
func testInsertNewTicket(t *testing.T) {
// Insert a ticket into the database.
ticket := exampleTicket()
err := db.InsertNewTicket(ticket)
if err != nil {
t.Fatalf("error storing ticket in database: %v", err)
}
// Insert another ticket.
err = db.InsertNewTicket(exampleTicket())
if err != nil {
t.Fatalf("error storing ticket in database: %v", err)
}
// Inserting a ticket with the same hash should fail.
ticket2 := exampleTicket()
ticket2.Hash = ticket.Hash
err = db.InsertNewTicket(ticket2)
if err == nil {
t.Fatal("expected an error inserting ticket with duplicate hash")
}
// Inserting a ticket with the same fee address should fail.
ticket3 := exampleTicket()
ticket3.FeeAddress = ticket.FeeAddress
err = db.InsertNewTicket(ticket3)
if err == nil {
t.Fatal("expected an error inserting ticket with duplicate fee addr")
}
// Inserting a ticket with empty hash should fail.
ticket.Hash = ""
err = db.InsertNewTicket(ticket)
if err == nil {
t.Fatal("expected an error inserting ticket with no hash")
}
}
func testDeleteTicket(t *testing.T) {
// Insert a ticket into the database.
ticket := exampleTicket()
err := db.InsertNewTicket(ticket)
if err != nil {
t.Fatalf("error storing ticket in database: %v", err)
}
// Delete ticket.
err = db.DeleteTicket(ticket)
if err != nil {
t.Fatalf("error deleting ticket: %v", err)
}
// Nothing should be in the db.
_, found, err := db.GetTicketByHash(ticket.Hash)
if err != nil {
t.Fatalf("error retrieving ticket by ticket hash: %v", err)
}
if found {
t.Fatal("expected found==false")
}
}
func testGetTicketByHash(t *testing.T) {
// Insert a ticket into the database.
ticket := exampleTicket()
err := db.InsertNewTicket(ticket)
if err != nil {
t.Fatalf("error storing ticket in database: %v", err)
}
// Retrieve ticket from database.
retrieved, found, err := db.GetTicketByHash(ticket.Hash)
if err != nil {
t.Fatalf("error retrieving ticket by ticket hash: %v", err)
}
if !found {
t.Fatal("expected found==true")
}
// Check ticket fields match expected.
if retrieved.Hash != ticket.Hash ||
retrieved.CommitmentAddress != ticket.CommitmentAddress ||
retrieved.FeeAddressIndex != ticket.FeeAddressIndex ||
retrieved.FeeAddress != ticket.FeeAddress ||
retrieved.FeeAmount != ticket.FeeAmount ||
retrieved.FeeExpiration != ticket.FeeExpiration ||
retrieved.Confirmed != ticket.Confirmed ||
!reflect.DeepEqual(retrieved.VoteChoices, ticket.VoteChoices) ||
retrieved.VotingWIF != ticket.VotingWIF ||
retrieved.FeeTxHex != ticket.FeeTxHex ||
retrieved.FeeTxHash != ticket.FeeTxHash ||
retrieved.FeeTxStatus != ticket.FeeTxStatus {
t.Fatal("retrieved ticket value didnt match expected")
}
// Check found==false when requesting a non-existent ticket.
_, found, err = db.GetTicketByHash("Not a real ticket hash")
if err != nil {
t.Fatalf("error retrieving ticket by ticket hash: %v", err)
}
if found {
t.Fatal("expected found==false")
}
}
func testUpdateTicket(t *testing.T) {
ticket := exampleTicket()
// Insert a ticket into the database.
err := db.InsertNewTicket(ticket)
if err != nil {
t.Fatalf("error storing ticket in database: %v", err)
}
// Update ticket with new values.
ticket.FeeAmount = ticket.FeeAmount + 1
ticket.FeeExpiration = ticket.FeeExpiration + 1
ticket.VoteChoices = map[string]string{"New agenda": "New value"}
err = db.UpdateTicket(ticket)
if err != nil {
t.Fatalf("error updating ticket: %v", err)
}
// Retrieve updated ticket from database.
retrieved, found, err := db.GetTicketByHash(ticket.Hash)
if err != nil {
t.Fatalf("error retrieving ticket by ticket hash: %v", err)
}
if !found {
t.Fatal("expected found==true")
}
if ticket.FeeAmount != retrieved.FeeAmount ||
ticket.FeeExpiration != retrieved.FeeExpiration ||
!reflect.DeepEqual(retrieved.VoteChoices, ticket.VoteChoices) {
t.Fatal("retrieved ticket value didnt match expected")
}
// Updating a non-existent ticket should fail.
ticket.Hash = "doesnt exist"
err = db.UpdateTicket(ticket)
if err == nil {
t.Fatal("expected an error updating a ticket with non-existent hash")
}
}
func testTicketFeeExpired(t *testing.T) {
ticket := exampleTicket()
now := time.Now()
hourBefore := now.Add(-time.Hour).Unix()
hourAfter := now.Add(time.Hour).Unix()
ticket.FeeExpiration = hourAfter
if ticket.FeeExpired() {
t.Fatal("expected ticket not to be expired")
}
ticket.FeeExpiration = hourBefore
if !ticket.FeeExpired() {
t.Fatal("expected ticket to be expired")
}
}
func testFilterTickets(t *testing.T) {
// Insert a ticket.
ticket := exampleTicket()
err := db.InsertNewTicket(ticket)
if err != nil {
t.Fatalf("error storing ticket in database: %v", err)
}
// Insert another ticket.
ticket2 := exampleTicket()
ticket2.Confirmed = !ticket.Confirmed
err = db.InsertNewTicket(ticket2)
if err != nil {
t.Fatalf("error storing ticket in database: %v", err)
}
// Expect all tickets returned.
retrieved, err := db.filterTickets(func(t *bolt.Bucket) bool {
return true
})
if err != nil {
t.Fatalf("error filtering tickets: %v", err)
}
if len(retrieved) != 2 {
t.Fatalf("expected to find 2 tickets, found %d", len(retrieved))
}
// Only one ticket should be confirmed.
retrieved, err = db.filterTickets(func(t *bolt.Bucket) bool {
return bytesToBool(t.Get(confirmedK))
})
if err != nil {
t.Fatalf("error filtering tickets: %v", err)
}
if len(retrieved) != 1 {
t.Fatalf("expected to find 1 ticket, found %d", len(retrieved))
}
if retrieved[0].Confirmed != true {
t.Fatal("expected retrieved ticket to be confirmed")
}
// Expect no tickets with confirmed fee.
retrieved, err = db.filterTickets(func(t *bolt.Bucket) bool {
return FeeStatus(t.Get(feeTxStatusK)) == FeeConfirmed
})
if err != nil {
t.Fatalf("error filtering tickets: %v", err)
}
if len(retrieved) != 0 {
t.Fatalf("expected to find 0 tickets, found %d", len(retrieved))
}
}
func testCountTickets(t *testing.T) {
count := func(test string, expectedVoting, expectedVoted, expectedRevoked int64) {
voting, voted, revoked, err := db.CountTickets()
if err != nil {
t.Fatalf("error counting tickets: %v", err)
}
if voting != expectedVoting {
t.Fatalf("test %s: expected %d voting tickets, got %d",
test, expectedVoting, voting)
}
if voted != expectedVoted {
t.Fatalf("test %s: expected %d voted tickets, got %d",
test, expectedVoted, voted)
}
if revoked != expectedRevoked {
t.Fatalf("test %s: expected %d revoked tickets, got %d",
test, expectedRevoked, revoked)
}
}
// Initial counts should all be zero.
count("empty db", 0, 0, 0)
// Insert a ticket with non-confirmed fee into the database.
// This should not be counted.
ticket := exampleTicket()
ticket.FeeTxStatus = FeeReceieved
err := db.InsertNewTicket(ticket)
if err != nil {
t.Fatalf("error storing ticket in database: %v", err)
}
count("unconfirmed fee", 0, 0, 0)
// Insert a ticket with confirmed fee into the database.
// This should be counted.
ticket2 := exampleTicket()
ticket2.FeeTxStatus = FeeConfirmed
err = db.InsertNewTicket(ticket2)
if err != nil {
t.Fatalf("error storing ticket in database: %v", err)
}
count("confirmed fee", 1, 0, 0)
// Insert a voted ticket into the database.
// This should be counted.
ticket3 := exampleTicket()
ticket3.FeeTxStatus = FeeConfirmed
ticket3.Outcome = Voted
err = db.InsertNewTicket(ticket3)
if err != nil {
t.Fatalf("error storing ticket in database: %v", err)
}
count("voted", 1, 1, 0)
// Insert a revoked ticket into the database.
// This should be counted.
ticket4 := exampleTicket()
ticket4.FeeTxStatus = FeeConfirmed
ticket4.Outcome = Revoked
err = db.InsertNewTicket(ticket4)
if err != nil {
t.Fatalf("error storing ticket in database: %v", err)
}
count("revoked", 1, 1, 1)
}