The new command opens an existing vspd database and replaces the currently used xpub with a new one.
158 lines
3.5 KiB
Go
158 lines
3.5 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"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
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
|
|
}
|
|
|
|
// RetireXPub will mark the currently active xpub key as retired and insert the
|
|
// provided pubkey as the currently active one.
|
|
func (vdb *VspDatabase) RetireXPub(xpub string) error {
|
|
// Ensure the new xpub has never been used before.
|
|
xpubs, err := vdb.AllXPubs()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, x := range xpubs {
|
|
if x.Key == xpub {
|
|
return errors.New("provided xpub has already been used")
|
|
}
|
|
}
|
|
|
|
current, err := vdb.FeeXPub()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
current.Retired = time.Now().Unix()
|
|
|
|
return vdb.db.Update(func(tx *bolt.Tx) error {
|
|
// Store the retired xpub.
|
|
err := insertFeeXPub(tx, current)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Insert new xpub.
|
|
newKey := FeeXPub{
|
|
ID: current.ID + 1,
|
|
Key: xpub,
|
|
LastUsedIdx: 0,
|
|
Retired: 0,
|
|
}
|
|
err = insertFeeXPub(tx, newKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return 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)
|
|
})
|
|
}
|