diff --git a/background/background.go b/background/background.go index 1b4bc69a..f02ba7cf 100644 --- a/background/background.go +++ b/background/background.go @@ -546,7 +546,7 @@ func checkWalletConsistency() { } } -func findOldestHeight(tickets []database.Ticket) int64 { +func findOldestHeight(tickets []*database.Ticket) int64 { var oldestHeight int64 for _, ticket := range tickets { // skip unconfirmed tickets diff --git a/database/database.go b/database/database.go index e2513f02..77779a3d 100644 --- a/database/database.go +++ b/database/database.go @@ -85,6 +85,16 @@ func writeHotBackupFile(db *bolt.DB) error { return err } +func int64ToBytes(i int64) []byte { + bytes := make([]byte, 8) + binary.LittleEndian.PutUint64(bytes, uint64(i)) + return bytes +} + +func bytesToInt64(bytes []byte) int64 { + return int64(binary.LittleEndian.Uint64(bytes)) +} + func uint32ToBytes(i uint32) []byte { bytes := make([]byte, 4) binary.LittleEndian.PutUint32(bytes, i) @@ -203,11 +213,11 @@ func Open(ctx context.Context, shutdownWg *sync.WaitGroup, dbFile string, backup return nil, fmt.Errorf("unable to get db version: %w", err) } - log.Debugf("Opened database (version=%d, file=%s)", dbVersion, dbFile) + log.Infof("Opened database (version=%d, file=%s)", dbVersion, dbFile) err = vdb.Upgrade(dbVersion) if err != nil { - return nil, fmt.Errorf("database upgrade failed: %w", err) + return nil, fmt.Errorf("upgrade failed: %w", err) } // Start a ticker to update the backup file at the specified interval. diff --git a/database/database_upgrades.go b/database/database_upgrades.go index c8e8af7e..36da5773 100644 --- a/database/database_upgrades.go +++ b/database/database_upgrades.go @@ -1,7 +1,10 @@ package database import ( + "encoding/json" "fmt" + + bolt "go.etcd.io/bbolt" ) const ( @@ -9,13 +12,23 @@ const ( // no upgrades applied. initialVersion = 1 - // latestVersion is the latest version of the bolt database that is - // understood by vspd. Databases with recorded versions higher than - // this will fail to open (meaning any upgrades prevent reverting to older - // software). - latestVersion = initialVersion + // ticketBucketVersion changes the way tickets are stored. Previously they + // were stored as JSON encoded strings in a single bucket. This upgrade + // moves each ticket into its own bucket and does away with JSON encoding. + ticketBucketVersion = 2 + + // latestVersion is the latest version of the database that is understood by + // vspd. Databases with recorded versions higher than this will fail to open + // (meaning any upgrades prevent reverting to older software). + latestVersion = ticketBucketVersion ) +// upgrades maps between old database versions and the upgrade function to +// upgrade the database to the next version. +var upgrades = []func(tx *bolt.DB) error{ + initialVersion: ticketBucketUpgrade, +} + // Upgrade will update the database to the latest known version. func (vdb *VspDatabase) Upgrade(currentVersion uint32) error { if currentVersion == latestVersion { @@ -25,8 +38,128 @@ func (vdb *VspDatabase) Upgrade(currentVersion uint32) error { if currentVersion > latestVersion { // Database is too new. - return fmt.Errorf("expected database version <= %d, got %d", latestVersion, currentVersion) + return fmt.Errorf("expected database version <= %d, got %d", + latestVersion, currentVersion) + } + + // Execute all necessary upgrades in order. + for _, upgrade := range upgrades[currentVersion:] { + err := upgrade(vdb.db) + if err != nil { + return err + } + } + + return nil +} + +func ticketBucketUpgrade(db *bolt.DB) error { + log.Infof("Upgrading database to version %d", ticketBucketVersion) + + // oldTicket has the json tags required to unmarshal tickets stored in the + // old database format. + type oldTicket struct { + Hash string `json:"hsh"` + PurchaseHeight int64 `json:"phgt"` + CommitmentAddress string `json:"cmtaddr"` + FeeAddressIndex uint32 `json:"faddridx"` + FeeAddress string `json:"faddr"` + FeeAmount int64 `json:"famt"` + FeeExpiration int64 `json:"fexp"` + Confirmed bool `json:"conf"` + VotingWIF string `json:"vwif"` + VoteChoices map[string]string `json:"vchces"` + FeeTxHex string `json:"fhex"` + FeeTxHash string `json:"fhsh"` + FeeTxStatus FeeStatus `json:"fsts"` + Outcome TicketOutcome `json:"otcme"` + } + + // Run the upgrade in a single database transaction so it can be safely + // rolled back if an error is encountered. + err := db.Update(func(tx *bolt.Tx) error { + vspBkt := tx.Bucket(vspBktK) + ticketBkt := vspBkt.Bucket(ticketBktK) + + // Count tickets so migration progress can be logged. + todo := 0 + err := ticketBkt.ForEach(func(k, v []byte) error { + todo++ + return nil + }) + if err != nil { + return fmt.Errorf("could not count tickets: %w", err) + } + + done := 0 + err = ticketBkt.ForEach(func(k, v []byte) error { + // Deserialize the old ticket. + var ticket oldTicket + err := json.Unmarshal(v, &ticket) + if err != nil { + return fmt.Errorf("could not unmarshal ticket: %w", err) + } + + // Delete the old ticket. + err = ticketBkt.Delete(k) + if err != nil { + return fmt.Errorf("could not delete ticket: %w", err) + } + + // Insert the new ticket. + newBkt, err := ticketBkt.CreateBucket(k) + if err != nil { + return fmt.Errorf("could not create new ticket bucket: %w", err) + } + + err = putTicketInBucket(newBkt, &Ticket{ + Hash: ticket.Hash, + PurchaseHeight: ticket.PurchaseHeight, + CommitmentAddress: ticket.CommitmentAddress, + FeeAddressIndex: ticket.FeeAddressIndex, + FeeAddress: ticket.FeeAddress, + FeeAmount: ticket.FeeAmount, + FeeExpiration: ticket.FeeExpiration, + Confirmed: ticket.Confirmed, + VotingWIF: ticket.VotingWIF, + VoteChoices: ticket.VoteChoices, + FeeTxHex: ticket.FeeTxHex, + FeeTxHash: ticket.FeeTxHash, + FeeTxStatus: ticket.FeeTxStatus, + Outcome: ticket.Outcome, + }) + if err != nil { + return fmt.Errorf("could not put new ticket in bucket: %w", err) + } + + done++ + + if done%2000 == 0 { + log.Infof("Migrated %d/%d tickets", done, todo) + } + + return nil + }) + if err != nil { + return err + } + + if done > 0 { + log.Infof("Migrated %d/%d tickets", done, todo) + } + + // Update database version. + err = vspBkt.Put(versionK, uint32ToBytes(ticketBucketVersion)) + if err != nil { + return err + } + + return nil + }) + if err != nil { + return err } + log.Info("Upgrade completed") return nil } diff --git a/database/ticket.go b/database/ticket.go index 632fbd0e..4db7a281 100644 --- a/database/ticket.go +++ b/database/ticket.go @@ -5,7 +5,6 @@ package database import ( - "encoding/json" "errors" "fmt" "time" @@ -39,37 +38,53 @@ const ( Voted TicketOutcome = "voted" ) -// 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. +// The keys used to store ticket values in the database. +var ( + HashK = []byte("Hash") + PurchaseHeightK = []byte("PurchaseHeight") + CommitmentAddressK = []byte("CommitmentAddress") + FeeAddressIndexK = []byte("FeeAddressIndex") + FeeAddressK = []byte("FeeAddress") + FeeAmountK = []byte("FeeAmount") + FeeExpirationK = []byte("FeeExpiration") + ConfirmedK = []byte("Confirmed") + VotingWIFK = []byte("VotingWIF") + VoteChoicesK = []byte("VoteChoices") + FeeTxHexK = []byte("FeeTxHex") + FeeTxHashK = []byte("FeeTxHash") + FeeTxStatusK = []byte("FeeTxStatus") + OutcomeK = []byte("Outcome") +) + type Ticket struct { - Hash string `json:"hsh"` - PurchaseHeight int64 `json:"phgt"` - CommitmentAddress string `json:"cmtaddr"` - FeeAddressIndex uint32 `json:"faddridx"` - FeeAddress string `json:"faddr"` - FeeAmount int64 `json:"famt"` - FeeExpiration int64 `json:"fexp"` + Hash string + PurchaseHeight int64 + CommitmentAddress string + FeeAddressIndex uint32 + FeeAddress string + FeeAmount int64 + FeeExpiration int64 // Confirmed will be set when the ticket has 6+ confirmations. - Confirmed bool `json:"conf"` + Confirmed bool // VotingWIF is set in /payfee. - VotingWIF string `json:"vwif"` + VotingWIF string // VoteChoices is initially set in /payfee, but can be updated in // /setvotechoices. - VoteChoices map[string]string `json:"vchces"` + VoteChoices map[string]string // FeeTxHex and FeeTxHash will be set when the fee tx has been received. - FeeTxHex string `json:"fhex"` - FeeTxHash string `json:"fhsh"` + FeeTxHex string + FeeTxHash string // FeeTxStatus indicates the current state of the fee transaction. - FeeTxStatus FeeStatus `json:"fsts"` + FeeTxStatus FeeStatus // Outcome is set once a ticket is either voted or revoked. An empty outcome // indicates that a ticket is still votable. - Outcome TicketOutcome `json:"otcme"` + Outcome TicketOutcome } func (t *Ticket) FeeExpired() bool { @@ -83,29 +98,26 @@ var ( // InsertNewTicket will insert the provided ticket into the database. Returns an // error if either the ticket hash or fee address already exist. -func (vdb *VspDatabase) InsertNewTicket(ticket Ticket) error { +func (vdb *VspDatabase) InsertNewTicket(ticket *Ticket) error { vdb.ticketsMtx.Lock() defer vdb.ticketsMtx.Unlock() 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) + // Create a bucket for the new ticket. Returns an error if bucket + // already exists. + newTicketBkt, err := ticketBkt.CreateBucket([]byte(ticket.Hash)) + if err != nil { + return fmt.Errorf("could not create bucket for ticket: %w", err) } // 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: %w", err) - } + err = ticketBkt.ForEach(func(k, v []byte) error { + tbkt := ticketBkt.Bucket(k) - if t.FeeAddress == ticket.FeeAddress { - return fmt.Errorf("ticket with fee address %s already exists", t.FeeAddress) + if string(tbkt.Get(FeeAddressK)) == ticket.FeeAddress { + return fmt.Errorf("ticket with fee address %s already exists", ticket.FeeAddress) } return nil @@ -114,23 +126,128 @@ func (vdb *VspDatabase) InsertNewTicket(ticket Ticket) error { return err } - ticketBytes, err := json.Marshal(ticket) + err = putTicketInBucket(newTicketBkt, ticket) if err != nil { - return fmt.Errorf("could not marshal ticket: %w", err) + return fmt.Errorf("putting ticket in bucket failed: %w", err) } - return ticketBkt.Put(hashBytes, ticketBytes) + return nil }) } -func (vdb *VspDatabase) DeleteTicket(ticket Ticket) error { +func putTicketInBucket(bkt *bolt.Bucket, ticket *Ticket) error { + var err error + if err = bkt.Put(HashK, []byte(ticket.Hash)); err != nil { + return err + } + if err = bkt.Put(CommitmentAddressK, []byte(ticket.CommitmentAddress)); err != nil { + return err + } + if err = bkt.Put(FeeAddressK, []byte(ticket.FeeAddress)); err != nil { + return err + } + if err = bkt.Put(VotingWIFK, []byte(ticket.VotingWIF)); err != nil { + return err + } + if err = bkt.Put(FeeTxHexK, []byte(ticket.FeeTxHex)); err != nil { + return err + } + if err = bkt.Put(FeeTxHashK, []byte(ticket.FeeTxHash)); err != nil { + return err + } + if err = bkt.Put(FeeTxStatusK, []byte(ticket.FeeTxStatus)); err != nil { + return err + } + if err = bkt.Put(OutcomeK, []byte(ticket.Outcome)); err != nil { + return err + } + if err = bkt.Put(PurchaseHeightK, int64ToBytes(ticket.PurchaseHeight)); err != nil { + return err + } + if err = bkt.Put(FeeAddressIndexK, uint32ToBytes(ticket.FeeAddressIndex)); err != nil { + return err + } + if err = bkt.Put(FeeAmountK, int64ToBytes(ticket.FeeAmount)); err != nil { + return err + } + if err = bkt.Put(FeeExpirationK, int64ToBytes(ticket.FeeExpiration)); err != nil { + return err + } + + confirmed := []byte{0} + if ticket.Confirmed { + confirmed = []byte{1} + } + if err = bkt.Put(ConfirmedK, confirmed); err != nil { + return err + } + + // Ensure a bucket for vote choices exists. + voteBkt, err := bkt.CreateBucketIfNotExists(VoteChoicesK) + if err != nil { + return err + } + + // Remove any existing vote choices. + err = voteBkt.ForEach(func(k, v []byte) error { + return voteBkt.Delete(k) + }) + if err != nil { + return err + } + + // Insert vote choices. + for agenda, choice := range ticket.VoteChoices { + if err = voteBkt.Put([]byte(agenda), []byte(choice)); err != nil { + return err + } + } + + return nil +} + +func getTicketFromBkt(bkt *bolt.Bucket) (*Ticket, error) { + var ticket Ticket + + ticket.Hash = string(bkt.Get(HashK)) + ticket.CommitmentAddress = string(bkt.Get(CommitmentAddressK)) + ticket.FeeAddress = string(bkt.Get(FeeAddressK)) + ticket.VotingWIF = string(bkt.Get(VotingWIFK)) + ticket.FeeTxHex = string(bkt.Get(FeeTxHexK)) + ticket.FeeTxHash = string(bkt.Get(FeeTxHashK)) + ticket.FeeTxStatus = FeeStatus(bkt.Get(FeeTxStatusK)) + ticket.Outcome = TicketOutcome(bkt.Get(OutcomeK)) + + ticket.PurchaseHeight = bytesToInt64(bkt.Get(PurchaseHeightK)) + ticket.FeeAddressIndex = bytesToUint32(bkt.Get(FeeAddressIndexK)) + ticket.FeeAmount = bytesToInt64(bkt.Get(FeeAmountK)) + ticket.FeeExpiration = bytesToInt64(bkt.Get(FeeExpirationK)) + + // TODO is this dodgy? + if bkt.Get(ConfirmedK)[0] == byte(1) { + ticket.Confirmed = true + } + + ticket.VoteChoices = make(map[string]string) + err := bkt.Bucket(VoteChoicesK).ForEach(func(k, v []byte) error { + ticket.VoteChoices[string(k)] = string(v) + return nil + }) + if err != nil { + return nil, err + } + + return &ticket, nil +} + +func (vdb *VspDatabase) DeleteTicket(ticket *Ticket) error { vdb.ticketsMtx.Lock() defer vdb.ticketsMtx.Unlock() return vdb.db.Update(func(tx *bolt.Tx) error { ticketBkt := tx.Bucket(vspBktK).Bucket(ticketBktK) - err := ticketBkt.Delete([]byte(ticket.Hash)) + err := ticketBkt.DeleteBucket([]byte(ticket.Hash)) if err != nil { return fmt.Errorf("could not delete ticket: %w", err) } @@ -139,45 +256,40 @@ func (vdb *VspDatabase) DeleteTicket(ticket Ticket) error { }) } -func (vdb *VspDatabase) UpdateTicket(ticket Ticket) error { +func (vdb *VspDatabase) UpdateTicket(ticket *Ticket) error { vdb.ticketsMtx.Lock() defer vdb.ticketsMtx.Unlock() return vdb.db.Update(func(tx *bolt.Tx) error { ticketBkt := tx.Bucket(vspBktK).Bucket(ticketBktK) - hashBytes := []byte(ticket.Hash) + bkt := ticketBkt.Bucket([]byte(ticket.Hash)) - if ticketBkt.Get(hashBytes) == nil { + if bkt == 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: %w", err) - } - - return ticketBkt.Put(hashBytes, ticketBytes) + return putTicketInBucket(bkt, ticket) }) } -func (vdb *VspDatabase) GetTicketByHash(ticketHash string) (Ticket, bool, error) { +func (vdb *VspDatabase) GetTicketByHash(ticketHash string) (*Ticket, bool, error) { vdb.ticketsMtx.RLock() defer vdb.ticketsMtx.RUnlock() - var ticket Ticket + var ticket *Ticket var found bool err := vdb.db.View(func(tx *bolt.Tx) error { - ticketBkt := tx.Bucket(vspBktK).Bucket(ticketBktK) + ticketBkt := tx.Bucket(vspBktK).Bucket(ticketBktK).Bucket([]byte(ticketHash)) - ticketBytes := ticketBkt.Get([]byte(ticketHash)) - if ticketBytes == nil { + if ticketBkt == nil { return nil } - err := json.Unmarshal(ticketBytes, &ticket) + var err error + ticket, err = getTicketFromBkt(ticketBkt) if err != nil { - return fmt.Errorf("could not unmarshal ticket: %w", err) + return fmt.Errorf("could not get ticket: %w", err) } found = true @@ -189,8 +301,7 @@ func (vdb *VspDatabase) GetTicketByHash(ticketHash string) (Ticket, bool, error) } // CountTickets returns the total number of voted, revoked, and currently voting -// tickets. Requires deserializing every ticket in the db so should be used -// sparingly. +// tickets. Iterates over every ticket in the db so should be used sparingly. func (vdb *VspDatabase) CountTickets() (int64, int64, int64, error) { vdb.ticketsMtx.RLock() defer vdb.ticketsMtx.RUnlock() @@ -200,14 +311,10 @@ func (vdb *VspDatabase) CountTickets() (int64, int64, int64, 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: %w", err) - } + tBkt := ticketBkt.Bucket(k) - if ticket.FeeTxStatus == FeeConfirmed { - switch ticket.Outcome { + if FeeStatus(tBkt.Get(FeeTxStatusK)) == FeeConfirmed { + switch TicketOutcome(tBkt.Get(OutcomeK)) { case Voted: voted++ case Revoked: @@ -225,41 +332,41 @@ func (vdb *VspDatabase) CountTickets() (int64, int64, int64, error) { } // GetUnconfirmedTickets returns tickets which are not yet confirmed. -func (vdb *VspDatabase) GetUnconfirmedTickets() ([]Ticket, error) { +func (vdb *VspDatabase) GetUnconfirmedTickets() ([]*Ticket, error) { vdb.ticketsMtx.RLock() defer vdb.ticketsMtx.RUnlock() - return vdb.filterTickets(func(t Ticket) bool { + 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) { +func (vdb *VspDatabase) GetPendingFees() ([]*Ticket, error) { vdb.ticketsMtx.RLock() defer vdb.ticketsMtx.RUnlock() - return vdb.filterTickets(func(t Ticket) bool { + 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) { +func (vdb *VspDatabase) GetUnconfirmedFees() ([]*Ticket, error) { vdb.ticketsMtx.RLock() defer vdb.ticketsMtx.RUnlock() - return vdb.filterTickets(func(t Ticket) bool { + return vdb.filterTickets(func(t *Ticket) bool { return t.FeeTxStatus == FeeBroadcast }) } // GetVotableTickets returns tickets with a confirmed fee tx and no outcome (ie. // not expired/voted/missed). -func (vdb *VspDatabase) GetVotableTickets() ([]Ticket, error) { - return vdb.filterTickets(func(t Ticket) bool { +func (vdb *VspDatabase) GetVotableTickets() ([]*Ticket, error) { + return vdb.filterTickets(func(t *Ticket) bool { return t.FeeTxStatus == FeeConfirmed && t.Outcome == "" }) } @@ -268,16 +375,15 @@ func (vdb *VspDatabase) GetVotableTickets() ([]Ticket, error) { // database which match the filter. // // This function must be called with the lock held. -func (vdb *VspDatabase) filterTickets(filter func(Ticket) bool) ([]Ticket, error) { - var tickets []Ticket +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) + ticket, err := getTicketFromBkt(ticketBkt.Bucket(k)) if err != nil { - return fmt.Errorf("could not unmarshal ticket: %w", err) + return fmt.Errorf("could not get ticket: %w", err) } if filter(ticket) { diff --git a/database/ticket_test.go b/database/ticket_test.go index d3953bb3..be1e23fd 100644 --- a/database/ticket_test.go +++ b/database/ticket_test.go @@ -23,11 +23,11 @@ func randString(length int, charset string) string { return string(b) } -func exampleTicket() Ticket { +func exampleTicket() *Ticket { const hexCharset = "1234567890abcdef" const addrCharset = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" - return Ticket{ + return &Ticket{ Hash: randString(64, hexCharset), CommitmentAddress: randString(35, addrCharset), FeeAddressIndex: 12345, @@ -159,12 +159,14 @@ func testUpdateTicket(t *testing.T) { // 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 ticket from database. + // 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) @@ -174,7 +176,8 @@ func testUpdateTicket(t *testing.T) { } if ticket.FeeAmount != retrieved.FeeAmount || - ticket.FeeExpiration != retrieved.FeeExpiration { + ticket.FeeExpiration != retrieved.FeeExpiration || + !reflect.DeepEqual(retrieved.VoteChoices, ticket.VoteChoices) { t.Fatal("retrieved ticket value didnt match expected") } @@ -221,7 +224,7 @@ func testFilterTickets(t *testing.T) { } // Expect all tickets returned. - retrieved, err := db.filterTickets(func(t Ticket) bool { + retrieved, err := db.filterTickets(func(t *Ticket) bool { return true }) if err != nil { @@ -232,7 +235,7 @@ func testFilterTickets(t *testing.T) { } // Only one ticket should be confirmed. - retrieved, err = db.filterTickets(func(t Ticket) bool { + retrieved, err = db.filterTickets(func(t *Ticket) bool { return t.Confirmed }) if err != nil { @@ -246,7 +249,7 @@ func testFilterTickets(t *testing.T) { } // Expect no tickets with confirmed fee. - retrieved, err = db.filterTickets(func(t Ticket) bool { + retrieved, err = db.filterTickets(func(t *Ticket) bool { return t.FeeTxStatus == FeeConfirmed }) if err != nil { diff --git a/webapi/getfeeaddress.go b/webapi/getfeeaddress.go index 1e5efa8b..e9bd43bb 100644 --- a/webapi/getfeeaddress.go +++ b/webapi/getfeeaddress.go @@ -68,7 +68,7 @@ func feeAddress(c *gin.Context) { const funcName = "feeAddress" // Get values which have been added to context by middleware. - ticket := c.MustGet("Ticket").(database.Ticket) + ticket := c.MustGet("Ticket").(*database.Ticket) knownTicket := c.MustGet("KnownTicket").(bool) commitmentAddress := c.MustGet("CommitmentAddress").(string) dcrdClient := c.MustGet("DcrdClient").(*rpc.DcrdRPC) @@ -89,9 +89,10 @@ func feeAddress(c *gin.Context) { ticketHash := request.TicketHash // Respond early if we already have the fee tx for this ticket. - if ticket.FeeTxStatus == database.FeeReceieved || - ticket.FeeTxStatus == database.FeeBroadcast || - ticket.FeeTxStatus == database.FeeConfirmed { + if knownTicket && + (ticket.FeeTxStatus == database.FeeReceieved || + ticket.FeeTxStatus == database.FeeBroadcast || + ticket.FeeTxStatus == database.FeeConfirmed) { log.Warnf("%s: Fee tx already received (clientIP=%s, ticketHash=%s)", funcName, c.ClientIP(), ticket.Hash) sendError(errFeeAlreadyReceived, c) @@ -178,7 +179,7 @@ func feeAddress(c *gin.Context) { confirmed := rawTicket.Confirmations >= requiredConfs - dbTicket := database.Ticket{ + dbTicket := &database.Ticket{ Hash: ticketHash, CommitmentAddress: commitmentAddress, FeeAddressIndex: newAddressIdx, diff --git a/webapi/payfee.go b/webapi/payfee.go index f2fe5dec..29ff774a 100644 --- a/webapi/payfee.go +++ b/webapi/payfee.go @@ -28,7 +28,7 @@ func payFee(c *gin.Context) { const funcName = "payFee" // Get values which have been added to context by middleware. - ticket := c.MustGet("Ticket").(database.Ticket) + ticket := c.MustGet("Ticket").(*database.Ticket) knownTicket := c.MustGet("KnownTicket").(bool) dcrdClient := c.MustGet("DcrdClient").(*rpc.DcrdRPC) reqBytes := c.MustGet("RequestBytes").([]byte) diff --git a/webapi/setvotechoices.go b/webapi/setvotechoices.go index 347789cd..4af5ed4f 100644 --- a/webapi/setvotechoices.go +++ b/webapi/setvotechoices.go @@ -24,7 +24,7 @@ func setVoteChoices(c *gin.Context) { const funcName = "setVoteChoices" // Get values which have been added to context by middleware. - ticket := c.MustGet("Ticket").(database.Ticket) + ticket := c.MustGet("Ticket").(*database.Ticket) knownTicket := c.MustGet("KnownTicket").(bool) walletClients := c.MustGet("WalletClients").([]*rpc.WalletRPC) reqBytes := c.MustGet("RequestBytes").([]byte) diff --git a/webapi/ticketstatus.go b/webapi/ticketstatus.go index 7840a2ce..e831d391 100644 --- a/webapi/ticketstatus.go +++ b/webapi/ticketstatus.go @@ -17,7 +17,7 @@ func ticketStatus(c *gin.Context) { const funcName = "ticketStatus" // Get values which have been added to context by middleware. - ticket := c.MustGet("Ticket").(database.Ticket) + ticket := c.MustGet("Ticket").(*database.Ticket) knownTicket := c.MustGet("KnownTicket").(bool) reqBytes := c.MustGet("RequestBytes").([]byte)