To allow signing with addresses other than the commitment address, add an alternate signature address. In order to continue to prove that the address was chosen by the user, add an alternate signature history. Only allow one record per ticket to be saved to cap needed db space.
306 lines
7.8 KiB
Go
306 lines
7.8 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 (
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
bolt "go.etcd.io/bbolt"
|
|
)
|
|
|
|
func exampleTicket() Ticket {
|
|
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 !reflect.DeepEqual(retrieved, ticket) {
|
|
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 !reflect.DeepEqual(retrieved, ticket) {
|
|
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)
|
|
}
|