232 lines
5.9 KiB
Go
232 lines
5.9 KiB
Go
package database
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
bolt "go.etcd.io/bbolt"
|
|
)
|
|
|
|
// FeeStatus represents the current state of a ticket fee payment.
|
|
type FeeStatus string
|
|
|
|
const (
|
|
// No fee transaction has been received yet.
|
|
NoFee FeeStatus = "none"
|
|
// Fee transaction has been received but not broadcast.
|
|
FeeReceieved FeeStatus = "received"
|
|
// Fee transaction has been broadcast but not confirmed.
|
|
FeeBroadcast FeeStatus = "broadcast"
|
|
// Fee transaction has been broadcast and confirmed.
|
|
FeeConfirmed FeeStatus = "confirmed"
|
|
// Fee transaction could not be broadcast due to an error.
|
|
FeeError FeeStatus = "error"
|
|
)
|
|
|
|
// Ticket is serialized to json and stored in bbolt db. The json keys are
|
|
// deliberately kept short because they are duplicated many times in the db.
|
|
type Ticket struct {
|
|
Hash string `json:"hsh"`
|
|
CommitmentAddress string `json:"cmtaddr"`
|
|
FeeAddressIndex uint32 `json:"faddridx"`
|
|
FeeAddress string `json:"faddr"`
|
|
FeeAmount int64 `json:"famt"`
|
|
FeeExpiration int64 `json:"fexp"`
|
|
|
|
// Confirmed will be set when the ticket has 6+ confirmations.
|
|
Confirmed bool `json:"conf"`
|
|
|
|
// VotingWIF is set in /payfee.
|
|
VotingWIF string `json:"vwif"`
|
|
|
|
// VoteChoices is initially set in /payfee, but can be updated in
|
|
// /setvotechoices.
|
|
VoteChoices map[string]string `json:"vchces"`
|
|
|
|
// FeeTxHex and FeeTxHash will be set when the fee tx has been received.
|
|
FeeTxHex string `json:"fhex"`
|
|
FeeTxHash string `json:"fhsh"`
|
|
|
|
// FeeTxStatus indicates the current state of the fee transaction.
|
|
FeeTxStatus FeeStatus `json:"fsts"`
|
|
}
|
|
|
|
func (t *Ticket) FeeExpired() bool {
|
|
now := time.Now()
|
|
return now.After(time.Unix(t.FeeExpiration, 0))
|
|
}
|
|
|
|
var (
|
|
ErrNoTicketFound = errors.New("no ticket found")
|
|
)
|
|
|
|
func (vdb *VspDatabase) InsertNewTicket(ticket Ticket) error {
|
|
return vdb.db.Update(func(tx *bolt.Tx) error {
|
|
ticketBkt := tx.Bucket(vspBktK).Bucket(ticketBktK)
|
|
|
|
hashBytes := []byte(ticket.Hash)
|
|
|
|
if ticketBkt.Get(hashBytes) != nil {
|
|
return fmt.Errorf("ticket already exists with hash %s", ticket.Hash)
|
|
}
|
|
|
|
// Error if a ticket already exists with the same fee address.
|
|
err := ticketBkt.ForEach(func(k, v []byte) error {
|
|
var t Ticket
|
|
err := json.Unmarshal(v, &t)
|
|
if err != nil {
|
|
return fmt.Errorf("could not unmarshal ticket: %v", err)
|
|
}
|
|
|
|
if t.FeeAddress == ticket.FeeAddress {
|
|
return fmt.Errorf("ticket with fee address %s already exists", t.FeeAddress)
|
|
}
|
|
|
|
if t.FeeAddressIndex == ticket.FeeAddressIndex {
|
|
return fmt.Errorf("ticket with fee address index %d already exists", t.FeeAddressIndex)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ticketBytes, err := json.Marshal(ticket)
|
|
if err != nil {
|
|
return fmt.Errorf("could not marshal ticket: %v", err)
|
|
}
|
|
|
|
return ticketBkt.Put(hashBytes, ticketBytes)
|
|
})
|
|
}
|
|
|
|
func (vdb *VspDatabase) DeleteTicket(ticket Ticket) error {
|
|
return vdb.db.Update(func(tx *bolt.Tx) error {
|
|
ticketBkt := tx.Bucket(vspBktK).Bucket(ticketBktK)
|
|
|
|
err := ticketBkt.Delete([]byte(ticket.Hash))
|
|
if err != nil {
|
|
return fmt.Errorf("could not delete ticket: %v", err)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
func (vdb *VspDatabase) UpdateTicket(ticket Ticket) error {
|
|
return vdb.db.Update(func(tx *bolt.Tx) error {
|
|
ticketBkt := tx.Bucket(vspBktK).Bucket(ticketBktK)
|
|
|
|
hashBytes := []byte(ticket.Hash)
|
|
|
|
if ticketBkt.Get(hashBytes) == nil {
|
|
return fmt.Errorf("ticket does not exist with hash %s", ticket.Hash)
|
|
}
|
|
|
|
ticketBytes, err := json.Marshal(ticket)
|
|
if err != nil {
|
|
return fmt.Errorf("could not marshal ticket: %v", err)
|
|
}
|
|
|
|
return ticketBkt.Put(hashBytes, ticketBytes)
|
|
})
|
|
}
|
|
|
|
func (vdb *VspDatabase) GetTicketByHash(ticketHash string) (Ticket, bool, error) {
|
|
var ticket Ticket
|
|
var found bool
|
|
err := vdb.db.View(func(tx *bolt.Tx) error {
|
|
ticketBkt := tx.Bucket(vspBktK).Bucket(ticketBktK)
|
|
|
|
ticketBytes := ticketBkt.Get([]byte(ticketHash))
|
|
if ticketBytes == nil {
|
|
return nil
|
|
}
|
|
|
|
err := json.Unmarshal(ticketBytes, &ticket)
|
|
if err != nil {
|
|
return fmt.Errorf("could not unmarshal ticket: %v", err)
|
|
}
|
|
|
|
found = true
|
|
|
|
return nil
|
|
})
|
|
|
|
return ticket, found, err
|
|
}
|
|
|
|
func (vdb *VspDatabase) CountTickets() (int, int, error) {
|
|
var total, feePaid int
|
|
err := vdb.db.View(func(tx *bolt.Tx) error {
|
|
ticketBkt := tx.Bucket(vspBktK).Bucket(ticketBktK)
|
|
|
|
return ticketBkt.ForEach(func(k, v []byte) error {
|
|
total++
|
|
var ticket Ticket
|
|
err := json.Unmarshal(v, &ticket)
|
|
if err != nil {
|
|
return fmt.Errorf("could not unmarshal ticket: %v", err)
|
|
}
|
|
|
|
if ticket.FeeTxStatus == FeeConfirmed {
|
|
feePaid++
|
|
}
|
|
|
|
return nil
|
|
})
|
|
})
|
|
|
|
return total, feePaid, err
|
|
}
|
|
|
|
// GetUnconfirmedTickets returns tickets which are not yet confirmed.
|
|
func (vdb *VspDatabase) GetUnconfirmedTickets() ([]Ticket, error) {
|
|
return vdb.filterTickets(func(t Ticket) bool {
|
|
return !t.Confirmed
|
|
})
|
|
}
|
|
|
|
// GetPendingFees returns tickets which are confirmed and have a fee tx which is
|
|
// not yet broadcast.
|
|
func (vdb *VspDatabase) GetPendingFees() ([]Ticket, error) {
|
|
return vdb.filterTickets(func(t Ticket) bool {
|
|
return t.Confirmed && t.FeeTxStatus == FeeReceieved
|
|
})
|
|
}
|
|
|
|
// GetUnconfirmedFees returns tickets with a fee tx that is broadcast but not
|
|
// confirmed yet.
|
|
func (vdb *VspDatabase) GetUnconfirmedFees() ([]Ticket, error) {
|
|
return vdb.filterTickets(func(t Ticket) bool {
|
|
return t.FeeTxStatus == FeeBroadcast
|
|
})
|
|
}
|
|
|
|
// filterTickets accepts a filter function and returns all tickets from the
|
|
// database which match the filter.
|
|
func (vdb *VspDatabase) filterTickets(filter func(Ticket) bool) ([]Ticket, error) {
|
|
var tickets []Ticket
|
|
err := vdb.db.View(func(tx *bolt.Tx) error {
|
|
ticketBkt := tx.Bucket(vspBktK).Bucket(ticketBktK)
|
|
|
|
return ticketBkt.ForEach(func(k, v []byte) error {
|
|
var ticket Ticket
|
|
err := json.Unmarshal(v, &ticket)
|
|
if err != nil {
|
|
return fmt.Errorf("could not unmarshal ticket: %v", err)
|
|
}
|
|
|
|
if filter(ticket) {
|
|
tickets = append(tickets, ticket)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
})
|
|
|
|
return tickets, err
|
|
}
|