Skip to content

Commit 87370e8

Browse files
committed
address+tapdb: add AddrByScriptKeyAndVersion
Because we'll use the address' script key as the bare/raw public key that will be tweaked for each individual output of an address v2 send, we'll need to be able to find addresses by that script key. We'll also make the script key unique for v2 addresses to avoid multiple records being returned here.
1 parent e97827a commit 87370e8

File tree

7 files changed

+278
-106
lines changed

7 files changed

+278
-106
lines changed

address/book.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,12 @@ type Storage interface {
127127
AddrByTaprootOutput(ctx context.Context,
128128
key *btcec.PublicKey) (*AddrWithKeyInfo, error)
129129

130+
// AddrByScriptKeyAndVersion returns a single address based on its
131+
// script key and version or a sql.ErrNoRows error if no such address
132+
// exists.
133+
AddrByScriptKeyAndVersion(context.Context, *btcec.PublicKey,
134+
Version) (*AddrWithKeyInfo, error)
135+
130136
// SetAddrManaged sets an address as being managed by the internal
131137
// wallet.
132138
SetAddrManaged(ctx context.Context, addr *AddrWithKeyInfo,
@@ -603,6 +609,14 @@ func (b *Book) AddrByTaprootOutput(ctx context.Context,
603609
return b.cfg.Store.AddrByTaprootOutput(ctx, key)
604610
}
605611

612+
// AddrByScriptKeyAndVersion returns a single address based on its script key
613+
// and version or a sql.ErrNoRows error if no such address exists.
614+
func (b *Book) AddrByScriptKeyAndVersion(ctx context.Context,
615+
scriptKey *btcec.PublicKey, version Version) (*AddrWithKeyInfo, error) {
616+
617+
return b.cfg.Store.AddrByScriptKeyAndVersion(ctx, scriptKey, version)
618+
}
619+
606620
// SetAddrManaged sets an address as being managed by the internal
607621
// wallet.
608622
func (b *Book) SetAddrManaged(ctx context.Context, addr *AddrWithKeyInfo,

address/book_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,13 @@ type MockStorage struct {
175175
mock.Mock
176176
}
177177

178+
func (m *MockStorage) AddrByScriptKeyAndVersion(ctx context.Context,
179+
key *btcec.PublicKey, version Version) (*AddrWithKeyInfo, error) {
180+
181+
args := m.Called(ctx, key, version)
182+
return args.Get(0).(*AddrWithKeyInfo), args.Error(1)
183+
}
184+
178185
func (m *MockStorage) GetOrCreateEvent(ctx context.Context, status Status,
179186
addr *AddrWithKeyInfo, walletTx *lndclient.Transaction,
180187
outputIdx uint32) (*Event, error) {

tapdb/addrs.go

Lines changed: 92 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,15 @@ type (
3838
// information.
3939
Addresses = sqlc.FetchAddrsRow
4040

41-
// AddrByTaprootOutput is a type alias for returning an address by its
42-
// Taproot output key.
43-
AddrByTaprootOutput = sqlc.FetchAddrByTaprootOutputKeyRow
41+
// SingleAddrQuery is a type alias for returning an address by its
42+
// Taproot output key, x-only script key or version (or a combination of
43+
// those).
44+
SingleAddrQuery = sqlc.QueryAddrParams
45+
46+
// SingleAddrRow is a type alias for returning an address by either its
47+
// Taproot output key, x-only script key or version (or a combination
48+
// of those).
49+
SingleAddrRow = sqlc.QueryAddrRow
4450

4551
// AddrManaged is a type alias for setting an address as managed.
4652
AddrManaged = sqlc.SetAddrManagedParams
@@ -111,11 +117,11 @@ type AddrBook interface {
111117
// passed AddrQuery.
112118
FetchAddrs(ctx context.Context, arg AddrQuery) ([]Addresses, error)
113119

114-
// FetchAddrByTaprootOutputKey returns a single address based on its
115-
// Taproot output key or a sql.ErrNoRows error if no such address
116-
// exists.
117-
FetchAddrByTaprootOutputKey(ctx context.Context,
118-
arg []byte) (AddrByTaprootOutput, error)
120+
// QueryAddr returns a single address based on its Taproot output key,
121+
// its x-only script key or version, or a sql.ErrNoRows error if no such
122+
// address exists.
123+
QueryAddr(ctx context.Context, arg SingleAddrQuery) (SingleAddrRow,
124+
error)
119125

120126
// UpsertAddr upserts a new address into the database returning the
121127
// primary key.
@@ -509,8 +515,21 @@ func (t *TapAddressBook) AddrByTaprootOutput(ctx context.Context,
509515
readOpts = NewAddrBookReadTx()
510516
)
511517
err := t.db.ExecTx(ctx, &readOpts, func(db AddrBook) error {
512-
var err error
513-
addr, err = fetchAddr(ctx, db, t.params, key)
518+
row, err := db.QueryAddr(ctx, SingleAddrQuery{
519+
TaprootOutputKey: schnorr.SerializePubKey(key),
520+
})
521+
switch {
522+
case errors.Is(err, sql.ErrNoRows):
523+
return address.ErrNoAddr
524+
525+
case err != nil:
526+
return err
527+
}
528+
529+
addr, err = parseAddr(
530+
ctx, db, t.params, row.Addr, row.ScriptKey,
531+
row.InternalKey, row.InternalKey_2,
532+
)
514533
return err
515534
})
516535
if err != nil {
@@ -520,22 +539,48 @@ func (t *TapAddressBook) AddrByTaprootOutput(ctx context.Context,
520539
return addr, nil
521540
}
522541

523-
// fetchAddr fetches a single address identified by its taproot output key from
524-
// the database and populates all its fields.
525-
func fetchAddr(ctx context.Context, db AddrBook, params *address.ChainParams,
526-
taprootOutputKey *btcec.PublicKey) (*address.AddrWithKeyInfo, error) {
542+
// AddrByScriptKeyAndVersion returns a single address based on its script key
543+
// and version or a sql.ErrNoRows error if no such address exists.
544+
func (t *TapAddressBook) AddrByScriptKeyAndVersion(ctx context.Context,
545+
scriptKey *btcec.PublicKey,
546+
version address.Version) (*address.AddrWithKeyInfo, error) {
527547

528-
dbAddr, err := db.FetchAddrByTaprootOutputKey(
529-
ctx, schnorr.SerializePubKey(taprootOutputKey),
548+
var (
549+
addr *address.AddrWithKeyInfo
550+
readOpts = NewAddrBookReadTx()
530551
)
531-
switch {
532-
case errors.Is(err, sql.ErrNoRows):
533-
return nil, address.ErrNoAddr
552+
err := t.db.ExecTx(ctx, &readOpts, func(db AddrBook) error {
553+
row, err := db.QueryAddr(ctx, SingleAddrQuery{
554+
XOnlyScriptKey: schnorr.SerializePubKey(scriptKey),
555+
Version: sqlInt16(version),
556+
})
557+
switch {
558+
case errors.Is(err, sql.ErrNoRows):
559+
return address.ErrNoAddr
534560

535-
case err != nil:
561+
case err != nil:
562+
return err
563+
}
564+
565+
addr, err = parseAddr(
566+
ctx, db, t.params, row.Addr, row.ScriptKey,
567+
row.InternalKey, row.InternalKey_2,
568+
)
569+
return err
570+
})
571+
if err != nil {
536572
return nil, err
537573
}
538574

575+
return addr, nil
576+
}
577+
578+
// fetchAddr fetches a single address identified by its taproot output key from
579+
// the database and populates all its fields.
580+
func parseAddr(ctx context.Context, db AddrBook, params *address.ChainParams,
581+
dbAddr sqlc.Addr, dbScriptKey sqlc.ScriptKey, dbInternalKey,
582+
dbTaprootKey sqlc.InternalKey) (*address.AddrWithKeyInfo, error) {
583+
539584
genesis, err := fetchGenesis(ctx, db, dbAddr.GenesisAssetID)
540585
if err != nil {
541586
return nil, fmt.Errorf("error fetching genesis: %w", err)
@@ -566,21 +611,21 @@ func fetchAddr(ctx context.Context, db AddrBook, params *address.ChainParams,
566611
}
567612
}
568613

569-
scriptKey, err := parseScriptKey(dbAddr.InternalKey, dbAddr.ScriptKey)
614+
scriptKey, err := parseScriptKey(dbInternalKey, dbScriptKey)
570615
if err != nil {
571616
return nil, fmt.Errorf("unable to decode script key: %w", err)
572617
}
573618

574-
internalKey, err := btcec.ParsePubKey(dbAddr.RawTaprootKey)
619+
internalKey, err := btcec.ParsePubKey(dbTaprootKey.RawKey)
575620
if err != nil {
576621
return nil, fmt.Errorf("unable to decode taproot key: %w", err)
577622
}
578623
internalKeyDesc := keychain.KeyDescriptor{
579624
KeyLocator: keychain.KeyLocator{
580625
Family: keychain.KeyFamily(
581-
dbAddr.TaprootKeyFamily,
626+
dbTaprootKey.KeyFamily,
582627
),
583-
Index: uint32(dbAddr.TaprootKeyIndex),
628+
Index: uint32(dbTaprootKey.KeyIndex),
584629
},
585630
PubKey: internalKey,
586631
}
@@ -612,6 +657,12 @@ func fetchAddr(ctx context.Context, db AddrBook, params *address.ChainParams,
612657
return nil, fmt.Errorf("unable to make addr: %w", err)
613658
}
614659

660+
taprootOutputKey, err := tapAddr.TaprootOutputKey()
661+
if err != nil {
662+
return nil, fmt.Errorf("unable to get taproot output key: %w",
663+
err)
664+
}
665+
615666
return &address.AddrWithKeyInfo{
616667
Tap: tapAddr,
617668
ScriptKeyTweak: *scriptKey.TweakedScriptKey,
@@ -866,11 +917,25 @@ func (t *TapAddressBook) QueryAddrEvents(
866917
"output key: %w", err)
867918
}
868919

869-
addr, err := fetchAddr(
870-
ctx, db, t.params, taprootOutputKey,
920+
row, err := db.QueryAddr(ctx, SingleAddrQuery{
921+
TaprootOutputKey: schnorr.SerializePubKey(
922+
taprootOutputKey,
923+
),
924+
})
925+
switch {
926+
case errors.Is(err, sql.ErrNoRows):
927+
return address.ErrNoAddr
928+
929+
case err != nil:
930+
return err
931+
}
932+
933+
addr, err := parseAddr(
934+
ctx, db, t.params, row.Addr, row.ScriptKey,
935+
row.InternalKey, row.InternalKey_2,
871936
)
872937
if err != nil {
873-
return fmt.Errorf("error fetching address: %w",
938+
return fmt.Errorf("error parsing address: %w",
874939
err)
875940
}
876941

tapdb/addrs_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -785,3 +785,87 @@ func TestScriptKeyTypeUpsert(t *testing.T) {
785785
)
786786
})
787787
}
788+
789+
// TestQueryAddrEvents tests that we can query address events by their taproot
790+
// output key.
791+
func TestQueryAddrEvents(t *testing.T) {
792+
t.Parallel()
793+
794+
// First, make a new addr book instance we'll use in the test below.
795+
testClock := clock.NewTestClock(time.Now())
796+
addrBook, _ := newAddrBook(t, testClock)
797+
798+
ctx := context.Background()
799+
800+
// Insert a test address and event into the database.
801+
proofCourierAddr := address.RandProofCourierAddr(t)
802+
addr, assetGen, assetGroup := address.RandAddr(
803+
t, chainParams, proofCourierAddr,
804+
)
805+
err := addrBook.db.ExecTx(
806+
ctx, WriteTxOption(),
807+
insertFullAssetGen(ctx, assetGen, assetGroup),
808+
)
809+
require.NoError(t, err)
810+
811+
err = addrBook.InsertAddrs(ctx, *addr)
812+
require.NoError(t, err)
813+
814+
tx := randWalletTx()
815+
event, err := addrBook.GetOrCreateEvent(
816+
ctx, address.StatusTransactionDetected, addr, tx, 0,
817+
)
818+
require.NoError(t, err)
819+
820+
// Query events for the address.
821+
queryParams := address.EventQueryParams{
822+
AddrTaprootOutputKey: schnorr.SerializePubKey(
823+
&addr.TaprootOutputKey,
824+
),
825+
}
826+
events, err := addrBook.QueryAddrEvents(ctx, queryParams)
827+
require.NoError(t, err)
828+
require.Len(t, events, 1)
829+
830+
// Verify the returned event matches the inserted event.
831+
require.Equal(t, event.ID, events[0].ID)
832+
require.Equal(t, event.Status, events[0].Status)
833+
require.Equal(t, event.Outpoint, events[0].Outpoint)
834+
}
835+
836+
// TestAddrByScriptKeyAndVersion tests that we can retrieve an address by its
837+
// script key and version.
838+
func TestAddrByScriptKeyAndVersion(t *testing.T) {
839+
t.Parallel()
840+
841+
// First, make a new addr book instance we'll use in the test below.
842+
testClock := clock.NewTestClock(time.Now())
843+
addrBook, _ := newAddrBook(t, testClock)
844+
845+
ctx := context.Background()
846+
847+
// Insert a test address into the database.
848+
proofCourierAddr := address.RandProofCourierAddr(t)
849+
addr, assetGen, assetGroup := address.RandAddr(
850+
t, chainParams, proofCourierAddr,
851+
)
852+
err := addrBook.db.ExecTx(
853+
ctx, WriteTxOption(),
854+
insertFullAssetGen(ctx, assetGen, assetGroup),
855+
)
856+
require.NoError(t, err)
857+
858+
err = addrBook.InsertAddrs(ctx, *addr)
859+
require.NoError(t, err)
860+
861+
// Query the address by script key and version.
862+
result, err := addrBook.AddrByScriptKeyAndVersion(
863+
ctx, &addr.ScriptKey, addr.Version,
864+
)
865+
require.NoError(t, err)
866+
require.NotNil(t, result)
867+
868+
// Verify the returned address matches the inserted address.
869+
require.Equal(t, addr.ScriptKey, result.ScriptKey)
870+
require.Equal(t, addr.Version, result.Version)
871+
}

0 commit comments

Comments
 (0)