vspd/internal/webapi/addressgenerator.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

93 lines
2.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 webapi
import (
"errors"
"github.com/decred/dcrd/chaincfg/v3"
"github.com/decred/dcrd/hdkeychain/v3"
"github.com/decred/dcrd/txscript/v4/stdaddr"
"github.com/decred/slog"
"github.com/decred/vspd/database"
)
type addressGenerator struct {
external *hdkeychain.ExtendedKey
netParams *chaincfg.Params
lastUsedIndex uint32
feeXPubID uint32
log slog.Logger
}
func newAddressGenerator(xPub database.FeeXPub, netParams *chaincfg.Params, log slog.Logger) (*addressGenerator, error) {
xPubKey, err := hdkeychain.NewKeyFromString(xPub.Key, netParams)
if err != nil {
return nil, err
}
if xPubKey.IsPrivate() {
return nil, errors.New("not a public key")
}
// Derive the extended key for the external chain.
external, err := xPubKey.Child(0)
if err != nil {
return nil, err
}
return &addressGenerator{
external: external,
netParams: netParams,
lastUsedIndex: xPub.LastUsedIdx,
feeXPubID: xPub.ID,
log: log,
}, nil
}
func (m *addressGenerator) xPubID() uint32 {
return m.feeXPubID
}
// nextAddress increments the last used address counter and returns a new
// address. It will skip any address index which causes an ErrInvalidChild.
// Not safe for concurrent access.
func (m *addressGenerator) nextAddress() (string, uint32, error) {
var key *hdkeychain.ExtendedKey
var err error
// There is a small chance that generating addresses for a given index can
// fail with ErrInvalidChild, so loop until we find an index which works.
// See the hdkeychain.ExtendedKey.Child docs for more info.
invalidChildren := 0
for {
m.lastUsedIndex++
key, err = m.external.Child(m.lastUsedIndex)
if err != nil {
if errors.Is(err, hdkeychain.ErrInvalidChild) {
invalidChildren++
m.log.Warnf("Generating address for index %d failed: %v", m.lastUsedIndex, err)
// If this happens 3 times, something is seriously wrong, so
// return an error.
if invalidChildren > 2 {
return "", 0, errors.New("multiple invalid children generated for key")
}
continue
}
return "", 0, err
}
break
}
// Convert to a standard pay-to-pubkey-hash address.
pkHash := stdaddr.Hash160(key.SerializedPubKey())
addr, err := stdaddr.NewAddressPubKeyHashEcdsaSecp256k1V0(pkHash, m.netParams)
if err != nil {
return "", 0, err
}
return addr.String(), m.lastUsedIndex, nil
}