vspd/database/ticket.go
Jamie Holdstock ccafd8dec4
Calculate fee from percentage. (#69)
* Calculate fee from percentage.

- Reverted config to accept a fee percentage, not absolute value.
- The fee amount to be paid is now included in the `getfeeaddress` response. The current best block is used to calculate the fee percentage, and new blocks may be mined before the fee is paid, so the fee expiry period is shortened from 24 hours to 1 hour to mitigate this.
- Rename ticket db field to FeeAmount so it is more representative of the data it holds.
- API fields renamed to "FeePercentage" and "FeeAmount"
- Relay fee is still hard coded.

* Use getbestblockhash
2020-05-27 14:44:40 +01:00

213 lines
5.0 KiB
Go

package database
import (
"encoding/json"
"errors"
"fmt"
"time"
bolt "go.etcd.io/bbolt"
)
// TODO: Properly document ticket lifecycle.
// TODO: Shorten json keys, they are stored in the db and duplicated many times.
type Ticket struct {
Hash string `json:"hash"`
CommitmentAddress string `json:"commitmentaddress"`
FeeAddressIndex uint32 `json:"feeaddressindex"`
FeeAddress string `json:"feeaddress"`
FeeAmount float64 `json:"feeamount"`
FeeExpiration int64 `json:"feeexpiration"`
// Confirmed will be set when the ticket has 6+ confirmations.
Confirmed bool `json:"confirmed"`
// VoteChoices and VotingWIF are set in /payfee.
VoteChoices map[string]string `json:"votechoices"`
VotingWIF string `json:"votingwif"`
// FeeTxHex will be set when the fee tx has been received from the user.
FeeTxHex string `json:"feetxhex"`
// FeeTxHash will be set when the fee tx has been broadcast.
FeeTxHash string `json:"feetxhash"`
// FeeConfirmed will be set when the fee tx has 6+ confirmations.
FeeConfirmed bool `json:"feeconfirmed"`
}
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)
}
// TODO: Error if a ticket already exists with the same fee address.
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) 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)
}
// TODO: Error if a ticket already exists with the same fee address.
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.FeeTxHash != "" {
feePaid++
}
return nil
})
})
return total, feePaid, err
}
func (vdb *VspDatabase) GetUnconfirmedTickets() ([]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 !ticket.Confirmed {
tickets = append(tickets, ticket)
}
return nil
})
})
return tickets, err
}
func (vdb *VspDatabase) GetPendingFees() ([]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)
}
// Add ticket if it is confirmed, and we have a fee tx, and the tx
// is not broadcast yet.
if ticket.Confirmed &&
ticket.FeeTxHex != "" &&
ticket.FeeTxHash == "" {
tickets = append(tickets, ticket)
}
return nil
})
})
return tickets, err
}
func (vdb *VspDatabase) GetUnconfirmedFees() ([]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)
}
// Add ticket if fee tx is broadcast but not confirmed yet.
if ticket.FeeTxHash != "" &&
!ticket.FeeConfirmed {
tickets = append(tickets, ticket)
}
return nil
})
})
return tickets, err
}