From dd991959b87e5b2f304ae28082f379072555726f Mon Sep 17 00:00:00 2001 From: jholdstock Date: Thu, 6 Jun 2024 12:49:08 +0100 Subject: [PATCH 1/3] database: Move xpub code to address index file. Moving the xpub accessors to the address index file is the first step towards combining these two concepts and treating them as a single object. --- database/addressindex.go | 29 +++++++++++++++++++++++++++-- database/addressindex_test.go | 13 +++++++++++-- database/database.go | 22 +--------------------- database/database_test.go | 14 ++------------ 4 files changed, 41 insertions(+), 37 deletions(-) diff --git a/database/addressindex.go b/database/addressindex.go index 8338d6da..a1524a53 100644 --- a/database/addressindex.go +++ b/database/addressindex.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Decred developers +// 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. @@ -8,6 +8,32 @@ import ( bolt "go.etcd.io/bbolt" ) +// insertFeeXPub stores the provided pubkey in the database, regardless of +// whether a value pre-exists. +func insertFeeXPub(tx *bolt.Tx, feeXPub string) error { + return tx.Bucket(vspBktK).Put(feeXPubK, []byte(feeXPub)) +} + +// FeeXPub retrieves the extended pubkey used for generating fee addresses +// from the database. +func (vdb *VspDatabase) FeeXPub() (string, error) { + var feeXPub string + err := vdb.db.View(func(tx *bolt.Tx) error { + vspBkt := tx.Bucket(vspBktK) + + xpubBytes := vspBkt.Get(feeXPubK) + if xpubBytes == nil { + return nil + } + + feeXPub = string(xpubBytes) + + return nil + }) + + return feeXPub, err +} + // GetLastAddressIndex retrieves the last index used to derive a new fee // address from the fee xpub key. func (vdb *VspDatabase) GetLastAddressIndex() (uint32, error) { @@ -34,7 +60,6 @@ func (vdb *VspDatabase) SetLastAddressIndex(idx uint32) error { err := vdb.db.Update(func(tx *bolt.Tx) error { vspBkt := tx.Bucket(vspBktK) return vspBkt.Put(lastAddressIndexK, uint32ToBytes(idx)) - }) return err diff --git a/database/addressindex_test.go b/database/addressindex_test.go index 75c1a58c..89e81fdd 100644 --- a/database/addressindex_test.go +++ b/database/addressindex_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Decred developers +// 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. @@ -8,7 +8,16 @@ import ( "testing" ) -func testAddressIndex(t *testing.T) { +func testFeeXPub(t *testing.T) { + // A newly created DB should store the fee xpub it was initialized with. + retrievedXPub, err := db.FeeXPub() + if err != nil { + t.Fatalf("error getting fee xpub: %v", err) + } + + if retrievedXPub != feeXPub { + t.Fatalf("expected fee xpub %v, got %v", feeXPub, retrievedXPub) + } // Getting index before it has been set should return 0. idx, err := db.GetLastAddressIndex() diff --git a/database/database.go b/database/database.go index 2c0fcff3..6c25cb0a 100644 --- a/database/database.go +++ b/database/database.go @@ -138,7 +138,7 @@ func CreateNew(dbFile, feeXPub string) error { } // Store fee xpub. - err = vspBkt.Put(feeXPubK, []byte(feeXPub)) + err = insertFeeXPub(tx, feeXPub) if err != nil { return err } @@ -311,26 +311,6 @@ func (vdb *VspDatabase) KeyPair() (ed25519.PrivateKey, ed25519.PublicKey, error) return signKey, pubKey, err } -// FeeXPub retrieves the extended pubkey used for generating fee addresses -// from the database. -func (vdb *VspDatabase) FeeXPub() (string, error) { - var feeXPub string - err := vdb.db.View(func(tx *bolt.Tx) error { - vspBkt := tx.Bucket(vspBktK) - - xpubBytes := vspBkt.Get(feeXPubK) - if xpubBytes == nil { - return nil - } - - feeXPub = string(xpubBytes) - - return nil - }) - - return feeXPub, err -} - // CookieSecret retrieves the generated cookie store secret key from the // database. func (vdb *VspDatabase) CookieSecret() ([]byte, error) { diff --git a/database/database_test.go b/database/database_test.go index a340f7a4..be5bdb58 100644 --- a/database/database_test.go +++ b/database/database_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2022 The Decred developers +// 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. @@ -77,7 +77,7 @@ func TestDatabase(t *testing.T) { "testTicketFeeExpired": testTicketFeeExpired, "testFilterTickets": testFilterTickets, "testCountTickets": testCountTickets, - "testAddressIndex": testAddressIndex, + "testFeeXPub": testFeeXPub, "testDeleteTicket": testDeleteTicket, "testVoteChangeRecords": testVoteChangeRecords, "testHTTPBackup": testHTTPBackup, @@ -134,16 +134,6 @@ func testCreateNew(t *testing.T) { if len(secret) != 32 { t.Fatalf("expected a 32 byte cookie secret, got %d bytes", len(secret)) } - - // A newly created DB should store the fee xpub it was initialized with. - retrievedXPub, err := db.FeeXPub() - if err != nil { - t.Fatalf("error getting fee xpub: %v", err) - } - - if retrievedXPub != feeXPub { - t.Fatalf("expected fee xpub %v, got %v", feeXPub, retrievedXPub) - } } func testHTTPBackup(t *testing.T) { From 54079a9ab09787572e776de534a5cc127d93dcdd Mon Sep 17 00:00:00 2001 From: jholdstock Date: Fri, 7 Jun 2024 13:19:21 +0100 Subject: [PATCH 2/3] database: Combine xpub and index concepts. Fee xpub key and last used address index are now wrapped into a struct, with both fields being set and retrieved together rather than individually. The underlying format for storing these values in the database does not change. The only change is the interface between the database code and the caller. --- database/addressindex.go | 57 +++++++++++++++++------------ database/addressindex_test.go | 20 ++++------ database/database.go | 6 ++- internal/webapi/addressgenerator.go | 9 +++-- internal/webapi/webapi.go | 14 +++---- 5 files changed, 57 insertions(+), 49 deletions(-) diff --git a/database/addressindex.go b/database/addressindex.go index a1524a53..df3ab7a6 100644 --- a/database/addressindex.go +++ b/database/addressindex.go @@ -5,62 +5,71 @@ package database import ( + "fmt" + bolt "go.etcd.io/bbolt" ) +type FeeXPub struct { + Key string + LastUsedIdx uint32 +} + // insertFeeXPub stores the provided pubkey in the database, regardless of // whether a value pre-exists. -func insertFeeXPub(tx *bolt.Tx, feeXPub string) error { - return tx.Bucket(vspBktK).Put(feeXPubK, []byte(feeXPub)) +func insertFeeXPub(tx *bolt.Tx, xpub FeeXPub) error { + vspBkt := tx.Bucket(vspBktK) + + err := vspBkt.Put(feeXPubK, []byte(xpub.Key)) + if err != nil { + return err + } + + return vspBkt.Put(lastAddressIndexK, uint32ToBytes(xpub.LastUsedIdx)) } // FeeXPub retrieves the extended pubkey used for generating fee addresses // from the database. -func (vdb *VspDatabase) FeeXPub() (string, error) { +func (vdb *VspDatabase) FeeXPub() (FeeXPub, error) { var feeXPub string + var idx uint32 err := vdb.db.View(func(tx *bolt.Tx) error { vspBkt := tx.Bucket(vspBktK) + // Get the key. xpubBytes := vspBkt.Get(feeXPubK) if xpubBytes == nil { return nil } - feeXPub = string(xpubBytes) - return nil - }) - - return feeXPub, err -} - -// GetLastAddressIndex retrieves the last index used to derive a new fee -// address from the fee xpub key. -func (vdb *VspDatabase) GetLastAddressIndex() (uint32, error) { - var idx uint32 - err := vdb.db.View(func(tx *bolt.Tx) error { - vspBkt := tx.Bucket(vspBktK) - + // Get the last used address index. idxBytes := vspBkt.Get(lastAddressIndexK) if idxBytes == nil { return nil } - idx = bytesToUint32(idxBytes) return nil }) + if err != nil { + return FeeXPub{}, fmt.Errorf("could not retrieve fee xpub: %w", err) + } - return idx, err + return FeeXPub{Key: feeXPub, LastUsedIdx: idx}, nil } // SetLastAddressIndex updates the last index used to derive a new fee address // from the fee xpub key. func (vdb *VspDatabase) SetLastAddressIndex(idx uint32) error { - err := vdb.db.Update(func(tx *bolt.Tx) error { - vspBkt := tx.Bucket(vspBktK) - return vspBkt.Put(lastAddressIndexK, uint32ToBytes(idx)) - }) + current, err := vdb.FeeXPub() + if err != nil { + return err + } - return err + current.LastUsedIdx = idx + + return vdb.db.Update(func(tx *bolt.Tx) error { + return insertFeeXPub(tx, current) + }) } diff --git a/database/addressindex_test.go b/database/addressindex_test.go index 89e81fdd..26804607 100644 --- a/database/addressindex_test.go +++ b/database/addressindex_test.go @@ -9,38 +9,34 @@ import ( ) func testFeeXPub(t *testing.T) { - // A newly created DB should store the fee xpub it was initialized with. + // A newly created DB should store the fee xpub it was initialized with, and + // the last used index should be 0. retrievedXPub, err := db.FeeXPub() if err != nil { t.Fatalf("error getting fee xpub: %v", err) } - if retrievedXPub != feeXPub { - t.Fatalf("expected fee xpub %v, got %v", feeXPub, retrievedXPub) + if retrievedXPub.Key != feeXPub { + t.Fatalf("expected fee xpub %v, got %v", feeXPub, retrievedXPub.Key) } - // Getting index before it has been set should return 0. - idx, err := db.GetLastAddressIndex() - if err != nil { - t.Fatalf("error getting address index: %v", err) - } - if idx != 0 { + if retrievedXPub.LastUsedIdx != 0 { t.Fatalf("retrieved addr index value didnt match expected") } // Update address index. - idx = uint32(99) + idx := uint32(99) err = db.SetLastAddressIndex(idx) if err != nil { t.Fatalf("error setting address index: %v", err) } // Check for updated value. - retrievedIdx, err := db.GetLastAddressIndex() + retrievedXPub, err = db.FeeXPub() if err != nil { t.Fatalf("error getting address index: %v", err) } - if idx != retrievedIdx { + if idx != retrievedXPub.LastUsedIdx { t.Fatalf("retrieved addr index value didnt match expected") } } diff --git a/database/database.go b/database/database.go index 6c25cb0a..11d3cfad 100644 --- a/database/database.go +++ b/database/database.go @@ -138,7 +138,11 @@ func CreateNew(dbFile, feeXPub string) error { } // Store fee xpub. - err = insertFeeXPub(tx, feeXPub) + xpub := FeeXPub{ + Key: feeXPub, + LastUsedIdx: 0, + } + err = insertFeeXPub(tx, xpub) if err != nil { return err } diff --git a/internal/webapi/addressgenerator.go b/internal/webapi/addressgenerator.go index 7d84cc94..73d7c7af 100644 --- a/internal/webapi/addressgenerator.go +++ b/internal/webapi/addressgenerator.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2022 The Decred developers +// 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. @@ -11,6 +11,7 @@ import ( "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 { @@ -20,8 +21,8 @@ type addressGenerator struct { log slog.Logger } -func newAddressGenerator(xPub string, netParams *chaincfg.Params, lastUsedIdx uint32, log slog.Logger) (*addressGenerator, error) { - xPubKey, err := hdkeychain.NewKeyFromString(xPub, netParams) +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 } @@ -39,7 +40,7 @@ func newAddressGenerator(xPub string, netParams *chaincfg.Params, lastUsedIdx ui return &addressGenerator{ external: external, netParams: netParams, - lastUsedIndex: lastUsedIdx, + lastUsedIndex: xPub.LastUsedIdx, log: log, }, nil } diff --git a/internal/webapi/webapi.go b/internal/webapi/webapi.go index 081899f2..9b501fd3 100644 --- a/internal/webapi/webapi.go +++ b/internal/webapi/webapi.go @@ -95,17 +95,15 @@ func New(vdb *database.VspDatabase, log slog.Logger, dcrd rpc.DcrdConnect, log.Errorf("Could not initialize VSP stats cache: %v", err) } - // Get the last used address index and the feeXpub from the database, and - // use them to initialize the address generator. - idx, err := vdb.GetLastAddressIndex() - if err != nil { - return nil, fmt.Errorf("db.GetLastAddressIndex error: %w", err) - } + // Get the current fee xpub details from the database. feeXPub, err := vdb.FeeXPub() if err != nil { - return nil, fmt.Errorf("db.GetFeeXPub error: %w", err) + return nil, fmt.Errorf("db.FeeXPub error: %w", err) } - addrGen, err := newAddressGenerator(feeXPub, cfg.Network.Params, idx, log) + + // Use the retrieved pubkey to initialize an address generator which can + // later be used to derive new fee addresses. + addrGen, err := newAddressGenerator(feeXPub, cfg.Network.Params, log) if err != nil { return nil, fmt.Errorf("failed to initialize fee address generator: %w", err) } From 9c170495f1a5dd14e694bcc105834e5879bf7fef Mon Sep 17 00:00:00 2001 From: jholdstock Date: Fri, 7 Jun 2024 13:19:37 +0100 Subject: [PATCH 3/3] database: Rename addressindex.go to feexpub.go --- database/{addressindex.go => feexpub.go} | 0 database/{addressindex_test.go => feexpub_test.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename database/{addressindex.go => feexpub.go} (100%) rename database/{addressindex_test.go => feexpub_test.go} (100%) diff --git a/database/addressindex.go b/database/feexpub.go similarity index 100% rename from database/addressindex.go rename to database/feexpub.go diff --git a/database/addressindex_test.go b/database/feexpub_test.go similarity index 100% rename from database/addressindex_test.go rename to database/feexpub_test.go