vspd/database/feexpub.go
jholdstock 4e4121335a database: Store xpub keys in a bucket.
**Warning: This commit contains a database upgrade.**

In order to add future support for retiring xpub keys, the database is
upgraded such that the keys are now stored in a dedicated bucket which
can hold multiple values rather than storing a single key as individual
values in the root bucket.

A new ID field is added to distinguish between keys. This ID is added to
every ticket record in the database in order to track which pubkey was
used for each ticket.

A new field named "Retired" has also been added to pubkeys. It is a unix
timestamp representing the moment the key was retired, or zero for the
currently active key.
2024-06-27 09:20:32 +01:00

113 lines
2.6 KiB
Go

// Copyright (c) 2020-2024 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 (
"encoding/json"
"fmt"
bolt "go.etcd.io/bbolt"
)
// FeeXPub is serialized to json and stored in bbolt db.
type FeeXPub struct {
ID uint32 `json:"id"`
Key string `json:"key"`
LastUsedIdx uint32 `json:"lastusedidx"`
// Retired is a unix timestamp representing the moment the key was retired,
// or zero for the currently active key.
Retired int64 `json:"retired"`
}
// insertFeeXPub stores the provided pubkey in the database, regardless of
// whether a value pre-exists.
func insertFeeXPub(tx *bolt.Tx, xpub FeeXPub) error {
vspBkt := tx.Bucket(vspBktK)
keyBkt, err := vspBkt.CreateBucketIfNotExists(xPubBktK)
if err != nil {
return fmt.Errorf("failed to get %s bucket: %w", string(xPubBktK), err)
}
keyBytes, err := json.Marshal(xpub)
if err != nil {
return fmt.Errorf("could not marshal xpub: %w", err)
}
err = keyBkt.Put(uint32ToBytes(xpub.ID), keyBytes)
if err != nil {
return fmt.Errorf("could not store xpub: %w", err)
}
return nil
}
// FeeXPub retrieves the currently active extended pubkey used for generating
// fee addresses from the database.
func (vdb *VspDatabase) FeeXPub() (FeeXPub, error) {
xpubs, err := vdb.AllXPubs()
if err != nil {
return FeeXPub{}, err
}
// Find the active xpub - the one with the highest ID.
var highest uint32
for id := range xpubs {
if id > highest {
highest = id
}
}
return xpubs[highest], nil
}
// AllXPubs retrieves the current and any retired extended pubkeys from the
// database.
func (vdb *VspDatabase) AllXPubs() (map[uint32]FeeXPub, error) {
xpubs := make(map[uint32]FeeXPub)
err := vdb.db.View(func(tx *bolt.Tx) error {
bkt := tx.Bucket(vspBktK).Bucket(xPubBktK)
if bkt == nil {
return fmt.Errorf("%s bucket doesn't exist", string(xPubBktK))
}
err := bkt.ForEach(func(k, v []byte) error {
var xpub FeeXPub
err := json.Unmarshal(v, &xpub)
if err != nil {
return fmt.Errorf("could not unmarshal xpub key: %w", err)
}
xpubs[bytesToUint32(k)] = xpub
return nil
})
if err != nil {
return fmt.Errorf("error iterating over %s bucket: %w", string(xPubBktK), err)
}
return nil
})
return xpubs, err
}
// SetLastAddressIndex updates the last index used to derive a new fee address
// from the fee xpub key.
func (vdb *VspDatabase) SetLastAddressIndex(idx uint32) error {
current, err := vdb.FeeXPub()
if err != nil {
return err
}
current.LastUsedIdx = idx
return vdb.db.Update(func(tx *bolt.Tx) error {
return insertFeeXPub(tx, current)
})
}