-
Notifications
You must be signed in to change notification settings - Fork 116
/
assets_store.go
3218 lines (2742 loc) · 93.6 KB
/
assets_store.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package tapdb
import (
"bytes"
"context"
"database/sql"
"errors"
"fmt"
"math"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/davecgh/go-spew/spew"
"github.com/lightninglabs/neutrino/cache/lru"
"github.com/lightninglabs/taproot-assets/asset"
"github.com/lightninglabs/taproot-assets/commitment"
"github.com/lightninglabs/taproot-assets/fn"
"github.com/lightninglabs/taproot-assets/mssmt"
"github.com/lightninglabs/taproot-assets/proof"
"github.com/lightninglabs/taproot-assets/tapdb/sqlc"
"github.com/lightninglabs/taproot-assets/tapfreighter"
"github.com/lightninglabs/taproot-assets/tappsbt"
"github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/keychain"
)
type (
// ConfirmedAsset is an asset that has been fully confirmed on chain.
ConfirmedAsset = sqlc.QueryAssetsRow
// RawAssetBalance holds a balance query result for a particular asset
// or all assets tracked by this daemon.
RawAssetBalance = sqlc.QueryAssetBalancesByAssetRow
// RawAssetGroupBalance holds a balance query result for a particular
// asset group or all asset groups tracked by this daemon.
RawAssetGroupBalance = sqlc.QueryAssetBalancesByGroupRow
// AssetProof is the asset proof for a given asset, identified by its
// script key.
AssetProof = sqlc.FetchAssetProofsRow
// AssetProofI is identical to AssetProof but is used for the case
// where the proofs for a specific asset are fetched.
AssetProofI = sqlc.FetchAssetProofRow
// FetchAssetProof are the query parameters for fetching an asset proof.
FetchAssetProof = sqlc.FetchAssetProofParams
// AssetProofByIDRow is the asset proof for a given asset, identified by
// its asset ID.
AssetProofByIDRow = sqlc.FetchAssetProofsByAssetIDRow
// PrevInput stores the full input information including the prev out,
// and also the witness information itself.
PrevInput = sqlc.UpsertAssetWitnessParams
// AssetWitness is the full prev input for an asset that also couples
// along the asset ID that the witness belong to.
AssetWitness = sqlc.FetchAssetWitnessesRow
// RawGroupedAsset holds the human-readable fields of a single asset
// with a non-nil group key.
RawGroupedAsset = sqlc.FetchGroupedAssetsRow
// QueryAssetFilters lets us query assets in the database based on some
// set filters. This is useful to get the balance of a set of assets,
// or for things like coin selection.
QueryAssetFilters = sqlc.QueryAssetsParams
// UtxoQuery lets us query a managed UTXO by either the transaction it
// references, or the outpoint.
UtxoQuery = sqlc.FetchManagedUTXOParams
// AnchorPoint wraps a managed UTXO along with all the auxiliary
// information it references.
AnchorPoint = sqlc.FetchManagedUTXORow
// ManagedUTXORow wraps a managed UTXO listing row.
ManagedUTXORow = sqlc.FetchManagedUTXOsRow
// UpdateUTXOLease wraps the params needed to lease a managed UTXO.
UpdateUTXOLease = sqlc.UpdateUTXOLeaseParams
// ApplyPendingOutput is used to update the script key and amount of an
// existing asset.
ApplyPendingOutput = sqlc.ApplyPendingOutputParams
// AnchorTxConf identifies an unconfirmed anchor tx to confirm.
AnchorTxConf = sqlc.ConfirmChainAnchorTxParams
// NewAssetTransfer wraps the params needed to insert a new asset
// transfer.
NewAssetTransfer = sqlc.InsertAssetTransferParams
// AssetTransfer tracks an asset transfer.
AssetTransfer = sqlc.AssetTransfer
// TransferQuery allows callers to filter out the set of transfers
// based on set information.
TransferQuery = sqlc.QueryAssetTransfersParams
// AssetTransferRow wraps a single transfer row.
AssetTransferRow = sqlc.QueryAssetTransfersRow
// TransferInput tracks the inputs to an asset transfer.
TransferInput = sqlc.AssetTransferInput
// TransferInputRow wraps a single transfer input row.
TransferInputRow = sqlc.FetchTransferInputsRow
// NewTransferInput wraps the params needed to insert a new transfer
// input.
NewTransferInput = sqlc.InsertAssetTransferInputParams
// TransferOutput tracks the outputs to an asset transfer.
TransferOutput = sqlc.AssetTransferOutput
// TransferOutputRow wraps a single transfer output row.
TransferOutputRow = sqlc.FetchTransferOutputsRow
// NewTransferOutput wraps the params needed to insert a new transfer
// output.
NewTransferOutput = sqlc.InsertAssetTransferOutputParams
// NewPassiveAsset wraps the params needed to insert a new passive
// asset.
NewPassiveAsset = sqlc.InsertPassiveAssetParams
// PassiveAsset tracks a passive asset.
PassiveAsset = sqlc.QueryPassiveAssetsRow
// ReAnchorParams wraps the params needed to re-anchor a passive asset.
ReAnchorParams = sqlc.ReAnchorPassiveAssetsParams
// LogProofTransAttemptParams is a type alias for the params needed to
// log a proof transfer attempt.
LogProofTransAttemptParams = sqlc.LogProofTransferAttemptParams
// QueryProofTransAttemptsParams is a type alias for the params needed
// to query the proof transfer attempts log.
QueryProofTransAttemptsParams = sqlc.QueryProofTransferAttemptsParams
// TapscriptTreeRootHash is a type alias for the params needed to insert
// a tapscript tree root hash.
TapscriptTreeRootHash = sqlc.UpsertTapscriptTreeRootHashParams
// TapscriptTreeEdge is a type alias for the params needed to insert an
// edge that links a tapscript tree node to a root hash, and records
// the order of the node in the tapscript tree.
TapscriptTreeEdge = sqlc.UpsertTapscriptTreeEdgeParams
// TapscriptTreeNode is a type alias for a tapscript tree node returned
// when fetching a tapscript tree, which includes the serialized node
// and the node index in the tree.
TapscriptTreeNode = sqlc.FetchTapscriptTreeRow
)
// ActiveAssetsStore is a sub-set of the main sqlc.Querier interface that
// contains methods related to querying the set of confirmed assets.
type ActiveAssetsStore interface {
// UpsertAssetStore houses the methods related to inserting/updating
// assets.
UpsertAssetStore
// QueryAssets fetches the set of fully confirmed assets.
QueryAssets(context.Context, QueryAssetFilters) ([]ConfirmedAsset,
error)
// QueryAssetBalancesByAsset queries the balances for assets or
// alternatively for a selected one that matches the passed asset ID
// filter.
QueryAssetBalancesByAsset(context.Context, []byte) ([]RawAssetBalance,
error)
// QueryAssetBalancesByGroup queries the asset balances for asset
// groups or alternatively for a selected one that matches the passed
// filter.
QueryAssetBalancesByGroup(context.Context,
[]byte) ([]RawAssetGroupBalance, error)
// FetchGroupedAssets fetches all assets with non-nil group keys.
FetchGroupedAssets(context.Context) ([]RawGroupedAsset, error)
// FetchAssetProofs fetches all the asset proofs we have stored on
// disk.
FetchAssetProofs(ctx context.Context) ([]AssetProof, error)
// FetchAssetProof fetches the asset proof for a given asset identified
// by its script key.
FetchAssetProof(ctx context.Context,
arg FetchAssetProof) ([]AssetProofI, error)
// HasAssetProof returns true if we have proof for a given asset
// identified by its script key.
HasAssetProof(ctx context.Context, scriptKey []byte) (bool, error)
// FetchAssetProofsByAssetID fetches all asset proofs for a given asset
// ID.
FetchAssetProofsByAssetID(ctx context.Context,
assetID []byte) ([]AssetProofByIDRow, error)
// UpsertChainTx inserts a new or updates an existing chain tx into the
// DB.
UpsertChainTx(ctx context.Context, arg ChainTxParams) (int64, error)
// FetchChainTx fetches a chain tx from the DB.
FetchChainTx(ctx context.Context, txid []byte) (ChainTx, error)
// UpsertManagedUTXO inserts a new or updates an existing managed UTXO
// to disk and returns the primary key.
UpsertManagedUTXO(ctx context.Context, arg RawManagedUTXO) (int64,
error)
// FetchAssetID fetches the `asset_id` (primary key) from the assets
// table for a given asset identified by `Outpoint` and
// `TweakedScriptKey`.
FetchAssetID(ctx context.Context, arg FetchAssetID) ([]int64, error)
// UpsertAssetProofByID inserts a new or updates an existing asset
// proof on disk.
UpsertAssetProofByID(ctx context.Context, arg ProofUpdateByID) error
// UpsertAssetWitness upserts a new prev input for an asset into the
// database.
UpsertAssetWitness(context.Context, PrevInput) error
// FetchAssetWitnesses attempts to fetch either all the asset witnesses
// on disk (NULL param), or the witness for a given asset ID.
FetchAssetWitnesses(context.Context, sql.NullInt64) ([]AssetWitness,
error)
// FetchManagedUTXO fetches a managed UTXO based on either the outpoint
// or the transaction that anchors it.
FetchManagedUTXO(context.Context, UtxoQuery) (AnchorPoint, error)
// FetchManagedUTXOs fetches all managed UTXOs.
FetchManagedUTXOs(context.Context) ([]ManagedUTXORow, error)
// ApplyPendingOutput applies a transfer output (new amount and script
// key) based on the existing script key of an asset.
ApplyPendingOutput(ctx context.Context, arg ApplyPendingOutput) (int64,
error)
// DeleteManagedUTXO deletes the managed utxo identified by the passed
// serialized outpoint.
DeleteManagedUTXO(ctx context.Context, outpoint []byte) error
// UpdateUTXOLease leases a managed UTXO identified by the passed
// serialized outpoint.
UpdateUTXOLease(ctx context.Context, arg UpdateUTXOLease) error
// DeleteUTXOLease deletes the lease on a managed UTXO identified by
// the passed serialized outpoint.
DeleteUTXOLease(ctx context.Context, outpoint []byte) error
// DeleteExpiredUTXOLeases deletes all expired UTXO leases.
DeleteExpiredUTXOLeases(ctx context.Context, now sql.NullTime) error
// ConfirmChainAnchorTx marks a new anchor transaction that was
// previously unconfirmed as confirmed.
ConfirmChainAnchorTx(ctx context.Context, arg AnchorTxConf) error
// InsertAssetTransfer inserts a new asset transfer into the DB.
InsertAssetTransfer(ctx context.Context,
arg NewAssetTransfer) (int64, error)
// InsertAssetTransferInput inserts a new asset transfer input into the
// DB.
InsertAssetTransferInput(ctx context.Context,
arg NewTransferInput) error
// InsertAssetTransferOutput inserts a new asset transfer output into
// the DB.
InsertAssetTransferOutput(ctx context.Context,
arg NewTransferOutput) error
// FetchTransferInputs fetches the inputs to a given asset transfer.
FetchTransferInputs(ctx context.Context,
transferID int64) ([]TransferInputRow, error)
// FetchTransferOutputs fetches the outputs to a given asset transfer.
FetchTransferOutputs(ctx context.Context,
transferID int64) ([]TransferOutputRow, error)
// QueryAssetTransfers queries for a set of asset transfers in the db.
QueryAssetTransfers(ctx context.Context,
query sqlc.QueryAssetTransfersParams) ([]AssetTransferRow,
error)
// DeleteAssetWitnesses deletes the witnesses on disk associated with a
// given asset ID.
DeleteAssetWitnesses(ctx context.Context, assetID int64) error
// LogProofTransferAttempt logs a new proof transfer attempt.
LogProofTransferAttempt(ctx context.Context,
arg LogProofTransAttemptParams) error
// QueryProofTransferAttempts returns timestamps from the proof transfer
// attempts log.
QueryProofTransferAttempts(ctx context.Context,
arg QueryProofTransAttemptsParams) ([]time.Time, error)
// InsertPassiveAsset inserts a new row which includes the data
// necessary to re-anchor a passive asset.
InsertPassiveAsset(ctx context.Context, arg NewPassiveAsset) error
// QueryPassiveAssets returns the data required to re-anchor
// pending passive assets that are anchored at the given outpoint.
QueryPassiveAssets(ctx context.Context,
transferID int64) ([]PassiveAsset, error)
// ReAnchorPassiveAssets re-anchors the passive assets identified by
// the passed params.
ReAnchorPassiveAssets(ctx context.Context, arg ReAnchorParams) error
// FetchAssetMetaByHash fetches the asset meta for a given meta hash.
//
// TODO(roasbeef): split into MetaStore?
FetchAssetMetaByHash(ctx context.Context,
metaDataHash []byte) (sqlc.FetchAssetMetaByHashRow, error)
// FetchAssetMetaForAsset fetches the asset meta for a given asset.
FetchAssetMetaForAsset(ctx context.Context,
assetID []byte) (sqlc.FetchAssetMetaForAssetRow, error)
}
// AssetBalance holds a balance query result for a particular asset or all
// assets tracked by this daemon.
type AssetBalance struct {
ID asset.ID
Balance uint64
Tag string
MetaHash [asset.MetaHashLen]byte
Type asset.Type
GenesisPoint wire.OutPoint
OutputIndex uint32
}
// AssetGroupBalance holds abalance query result for a particular asset group
// or all asset groups tracked by this daemon.
type AssetGroupBalance struct {
GroupKey *btcec.PublicKey
Balance uint64
}
// cacheableTimestamp is a wrapper around an int32 that can be used as a
// value in an LRU cache.
type cacheableBlockHeight uint32
// Size returns the size of the cacheable block height. Since we scale the cache
// by the number of items and not the total memory size, we can simply return 1
// here to count each timestamp as 1 item.
func (c cacheableBlockHeight) Size() (uint64, error) {
return 1, nil
}
// BatchedAssetStore combines the AssetStore interface with the BatchedTx
// interface, allowing for multiple queries to be executed in a single SQL
// transaction.
type BatchedAssetStore interface {
ActiveAssetsStore
BatchedTx[ActiveAssetsStore]
}
// AssetStore is used to query for the set of pending and confirmed assets.
type AssetStore struct {
db BatchedAssetStore
// eventDistributor is an event distributor that will be used to notify
// subscribers about new proofs that are added to the archiver.
eventDistributor *fn.EventDistributor[proof.Blob]
clock clock.Clock
txHeights *lru.Cache[chainhash.Hash, cacheableBlockHeight]
}
// NewAssetStore creates a new AssetStore from the specified BatchedAssetStore
// interface.
func NewAssetStore(db BatchedAssetStore, clock clock.Clock) *AssetStore {
return &AssetStore{
db: db,
eventDistributor: fn.NewEventDistributor[proof.Blob](),
clock: clock,
txHeights: lru.NewCache[chainhash.Hash, cacheableBlockHeight](
10_000,
),
}
}
// ManagedUTXO holds information about a given UTXO we manage.
type ManagedUTXO struct {
// OutPoint is the outpoint of the UTXO.
OutPoint wire.OutPoint
// OutputValue is the satoshi output value of the UTXO.
OutputValue btcutil.Amount
// InternalKey is the internal key that's used to anchor the commitment
// in the outpoint.
InternalKey keychain.KeyDescriptor
// TaprootAssetRoot is the Taproot Asset commitment root hash committed
// to by this outpoint.
TaprootAssetRoot []byte
// MerkleRoot is the Taproot merkle root hash committed to by this
// outpoint. If there is no Tapscript sibling, this is equal to
// TaprootAssetRoot.
MerkleRoot []byte
// TapscriptSibling is the serialized tapscript sibling preimage of
// this asset. This will usually be blank.
TapscriptSibling []byte
}
// AssetHumanReadable is a subset of the base asset struct that only includes
// human-readable asset fields.
type AssetHumanReadable struct {
// ID is the unique identifier for the asset.
ID asset.ID
// Version is the version of the asset.
Version asset.Version
// Amount is the number of units represented by the asset.
Amount uint64
// LockTime, if non-zero, restricts an asset from being moved prior to
// the represented block height in the chain.
LockTime uint64
// RelativeLockTime, if non-zero, restricts an asset from being moved
// until a number of blocks after the confirmation height of the latest
// transaction for the asset is reached.
RelativeLockTime uint64
// Tag is the human-readable identifier for the asset.
Tag string
// MetaHash is the hash of the meta data for this asset.
MetaHash [asset.MetaHashLen]byte
// Type uniquely identifies the type of Taproot asset.
Type asset.Type
// GroupKey is the tweaked public key that is used to associate assets
// together across distinct asset IDs.
GroupKey *btcec.PublicKey
}
// assetWitnesses maps the primary key of an asset to a slice of its previous
// input (witness) information.
type assetWitnesses map[int64][]AssetWitness
// fetchAssetWitnesses attempts to fetch all the asset witnesses that belong to
// the set of passed asset IDs.
func fetchAssetWitnesses(ctx context.Context, db ActiveAssetsStore,
assetIDs []int64) (assetWitnesses, error) {
assetWitnesses := make(map[int64][]AssetWitness)
for _, assetID := range assetIDs {
witnesses, err := db.FetchAssetWitnesses(
ctx, sqlInt64(assetID),
)
if err != nil {
return nil, err
}
// We'll insert a nil witness for genesis asset, so we don't
// add it to the map, which'll give it the genesis witness.
if len(witnesses) == 0 {
continue
}
assetWitnesses[assetID] = witnesses
}
return assetWitnesses, nil
}
// parseAssetWitness maps a witness stored in the database to something we can
// use directly.
func parseAssetWitness(input AssetWitness) (asset.Witness, error) {
var (
op wire.OutPoint
witness asset.Witness
)
err := readOutPoint(
bytes.NewReader(input.PrevOutPoint), 0, 0, &op,
)
if err != nil {
return witness, fmt.Errorf("unable to "+
"read outpoint: %w", err)
}
var (
zeroKey, scriptKey asset.SerializedKey
)
if !bytes.Equal(zeroKey[:], input.PrevScriptKey) {
prevKey, err := btcec.ParsePubKey(input.PrevScriptKey)
if err != nil {
return witness, fmt.Errorf("unable to decode key: %w",
err)
}
scriptKey = asset.ToSerialized(prevKey)
}
var assetID asset.ID
copy(assetID[:], input.PrevAssetID)
witness.PrevID = &asset.PrevID{
OutPoint: op,
ID: assetID,
ScriptKey: scriptKey,
}
var buf [8]byte
if len(input.WitnessStack) != 0 {
err = asset.TxWitnessDecoder(
bytes.NewReader(input.WitnessStack),
&witness.TxWitness, &buf,
uint64(len(input.WitnessStack)),
)
if err != nil {
return witness, fmt.Errorf("unable to decode "+
"witness: %w", err)
}
}
if len(input.SplitCommitmentProof) != 0 {
err := asset.SplitCommitmentDecoder(
bytes.NewReader(input.SplitCommitmentProof),
&witness.SplitCommitment, &buf,
uint64(len(input.SplitCommitmentProof)),
)
if err != nil {
return witness, fmt.Errorf("unable to decode split "+
"commitment: %w", err)
}
}
return witness, nil
}
// dbAssetsToChainAssets maps a set of confirmed assets in the database, and
// the witnesses of those assets to a set of normal ChainAsset structs needed
// by a higher level application.
func (a *AssetStore) dbAssetsToChainAssets(dbAssets []ConfirmedAsset,
witnesses assetWitnesses) ([]*asset.ChainAsset, error) {
chainAssets := make([]*asset.ChainAsset, len(dbAssets))
for i := range dbAssets {
sprout := dbAssets[i]
// First, we'll decode the script key which every asset must
// specify, and populate the key locator information.
rawScriptKeyPub, err := btcec.ParsePubKey(sprout.ScriptKeyRaw)
if err != nil {
return nil, err
}
rawScriptKeyDesc := keychain.KeyDescriptor{
PubKey: rawScriptKeyPub,
KeyLocator: keychain.KeyLocator{
Index: uint32(sprout.ScriptKeyIndex),
Family: keychain.KeyFamily(sprout.ScriptKeyFam),
},
}
// Not all assets have a key group, so we only need to
// populate this information for those that signalled the
// requirement of ongoing emission.
var groupKey *asset.GroupKey
if sprout.TweakedGroupKey != nil {
tweakedGroupKey, err := btcec.ParsePubKey(
sprout.TweakedGroupKey,
)
if err != nil {
return nil, err
}
rawGroupKey, err := btcec.ParsePubKey(sprout.GroupKeyRaw)
if err != nil {
return nil, err
}
groupWitness, err := asset.ParseGroupWitness(
sprout.WitnessStack,
)
if err != nil {
return nil, err
}
var tapscriptRoot []byte
if len(sprout.TapscriptRoot) != 0 {
tapscriptRoot = sprout.TapscriptRoot
}
groupKey = &asset.GroupKey{
RawKey: keychain.KeyDescriptor{
PubKey: rawGroupKey,
KeyLocator: keychain.KeyLocator{
Index: extractSqlInt32[uint32](
sprout.GroupKeyIndex,
),
Family: extractSqlInt32[keychain.KeyFamily](
sprout.GroupKeyFamily,
),
},
},
GroupPubKey: *tweakedGroupKey,
Witness: groupWitness,
TapscriptRoot: tapscriptRoot,
}
}
// Next, we'll populate the asset genesis information which
// includes the genesis prev out, and the other information
// needed to derive an asset ID.
var genesisPrevOut wire.OutPoint
if err := readOutPoint(
bytes.NewReader(sprout.GenesisPrevOut), 0, 0,
&genesisPrevOut,
); err != nil {
return nil, fmt.Errorf("unable to read "+
"outpoint: %w", err)
}
assetGenesis := asset.Genesis{
FirstPrevOut: genesisPrevOut,
Tag: sprout.AssetTag,
OutputIndex: uint32(sprout.GenesisOutputIndex),
Type: asset.Type(sprout.AssetType),
}
if len(sprout.MetaHash) != 0 {
copy(assetGenesis.MetaHash[:], sprout.MetaHash)
}
// With the base information extracted, we'll use that to
// create either a normal asset or a collectible.
lockTime := extractSqlInt32[uint64](sprout.LockTime)
relativeLocktime := extractSqlInt32[uint64](
sprout.RelativeLockTime,
)
var amount uint64
switch asset.Type(sprout.AssetType) {
case asset.Normal:
amount = uint64(sprout.Amount)
case asset.Collectible:
amount = 1
}
scriptKeyPub, err := btcec.ParsePubKey(sprout.TweakedScriptKey)
if err != nil {
return nil, err
}
declaredKnown := sprout.ScriptKeyDeclaredKnown.Valid
scriptKey := asset.ScriptKey{
PubKey: scriptKeyPub,
TweakedScriptKey: &asset.TweakedScriptKey{
RawKey: rawScriptKeyDesc,
Tweak: sprout.ScriptKeyTweak,
DeclaredKnown: declaredKnown,
},
}
assetSprout, err := asset.New(
assetGenesis, amount, lockTime, relativeLocktime,
scriptKey, groupKey,
asset.WithAssetVersion(asset.Version(sprout.Version)),
)
if err != nil {
return nil, fmt.Errorf("unable to create new sprout: "+
"%w", err)
}
// We cannot use 0 as the amount when creating a new asset with
// the New function above. But if this is a tombstone asset, we
// actually have to set the amount to 0.
if scriptKeyPub.IsEqual(asset.NUMSPubKey) && sprout.Amount == 0 {
assetSprout.Amount = 0
}
if len(sprout.SplitCommitmentRootHash) != 0 {
var nodeHash mssmt.NodeHash
copy(nodeHash[:], sprout.SplitCommitmentRootHash)
assetSprout.SplitCommitmentRoot = mssmt.NewComputedNode(
nodeHash,
uint64(sprout.SplitCommitmentRootValue.Int64),
)
}
// With the asset created, we'll now emplace the set of
// witnesses for the asset itself. If this is a genesis asset,
// then it won't have a set of witnesses.
assetInputs, ok := witnesses[sprout.AssetPrimaryKey]
if ok {
assetSprout.PrevWitnesses = make(
[]asset.Witness, 0, len(assetInputs),
)
for _, input := range assetInputs {
witness, err := parseAssetWitness(input)
if err != nil {
return nil, fmt.Errorf("unable to "+
"parse witness: %w", err)
}
assetSprout.PrevWitnesses = append(
assetSprout.PrevWitnesses, witness,
)
}
}
anchorTx := wire.NewMsgTx(2)
err = anchorTx.Deserialize(bytes.NewBuffer(sprout.AnchorTx))
if err != nil {
return nil, fmt.Errorf("unable to decode tx: %w", err)
}
// An asset will only have an anchor block hash once it has
// confirmed, so we'll only parse this if it exists.
var anchorBlockHash chainhash.Hash
if sprout.AnchorBlockHash != nil {
anchorHash, err := chainhash.NewHash(
sprout.AnchorBlockHash,
)
if err != nil {
return nil, fmt.Errorf("unable to extract block "+
"hash: %w", err)
}
anchorBlockHash = *anchorHash
}
var anchorOutpoint wire.OutPoint
err = readOutPoint(
bytes.NewReader(sprout.AnchorOutpoint), 0, 0,
&anchorOutpoint,
)
if err != nil {
return nil, fmt.Errorf("unable to decode "+
"outpoint: %w", err)
}
anchorInternalKey, err := btcec.ParsePubKey(
sprout.AnchorInternalKey,
)
if err != nil {
return nil, fmt.Errorf("unable to parse anchor "+
"internal key: %w", err)
}
chainAssets[i] = &asset.ChainAsset{
Asset: assetSprout,
IsSpent: sprout.Spent,
AnchorTx: anchorTx,
AnchorBlockHash: anchorBlockHash,
AnchorOutpoint: anchorOutpoint,
AnchorInternalKey: anchorInternalKey,
AnchorMerkleRoot: sprout.AnchorMerkleRoot,
AnchorTapscriptSibling: sprout.AnchorTapscriptSibling,
}
// We only set the lease info if the lease is actually still
// valid and hasn't expired.
owner := sprout.AnchorLeaseOwner
expiry := sprout.AnchorLeaseExpiry
if len(owner) > 0 && expiry.Valid &&
expiry.Time.UTC().After(a.clock.Now().UTC()) {
copy(chainAssets[i].AnchorLeaseOwner[:], owner)
chainAssets[i].AnchorLeaseExpiry = &expiry.Time
}
}
return chainAssets, nil
}
// constraintsToDbFilter maps application level constraints to the set of
// filters we use in the SQL queries.
func (a *AssetStore) constraintsToDbFilter(
query *AssetQueryFilters) QueryAssetFilters {
assetFilter := QueryAssetFilters{
Now: sql.NullTime{
Time: a.clock.Now().UTC(),
Valid: true,
},
}
if query != nil {
if query.MinAmt != 0 {
assetFilter.MinAmt = sql.NullInt64{
Int64: int64(query.MinAmt),
Valid: true,
}
}
if query.MinAnchorHeight != 0 {
assetFilter.MinAnchorHeight = sqlInt32(
query.MinAnchorHeight,
)
}
if query.AssetID != nil {
assetID := query.AssetID[:]
assetFilter.AssetIDFilter = assetID
}
if query.GroupKey != nil {
groupKey := query.GroupKey.SerializeCompressed()
assetFilter.KeyGroupFilter = groupKey
}
// TODO(roasbeef): only want to allow asset ID or other and not
// both?
if query.Bip86ScriptKeysOnly {
assetFilter.Bip86ScriptKeysOnly = true
}
}
return assetFilter
}
// specificAssetFilter maps the given asset parameters to the set of filters
// we use in the SQL queries.
func (a *AssetStore) specificAssetFilter(id asset.ID, anchorPoint wire.OutPoint,
groupKey *asset.GroupKey,
scriptKey *asset.ScriptKey) (QueryAssetFilters, error) {
anchorPointBytes, err := encodeOutpoint(anchorPoint)
if err != nil {
return QueryAssetFilters{}, fmt.Errorf("unable to encode "+
"outpoint: %w", err)
}
filter := QueryAssetFilters{
AssetIDFilter: id[:],
AnchorPoint: anchorPointBytes,
Now: sql.NullTime{
Time: a.clock.Now().UTC(),
Valid: true,
},
}
if groupKey != nil {
key := groupKey.GroupPubKey
filter.KeyGroupFilter = key.SerializeCompressed()
}
if scriptKey != nil {
key := scriptKey.PubKey
filter.TweakedScriptKey = key.SerializeCompressed()
}
return filter, nil
}
// fetchAssetsWithWitness fetches the set of assets in the backing store based
// on the set asset filter. A set of witnesses for each of the assets keyed by
// the primary key of the asset is also returned.
func fetchAssetsWithWitness(ctx context.Context, q ActiveAssetsStore,
assetFilter QueryAssetFilters) ([]ConfirmedAsset, assetWitnesses,
error) {
// First, we'll fetch all the assets we know of on disk.
dbAssets, err := q.QueryAssets(ctx, assetFilter)
if err != nil {
return nil, nil, fmt.Errorf("unable to read db assets: %w", err)
}
assetIDs := fMap(dbAssets, func(a ConfirmedAsset) int64 {
return a.AssetPrimaryKey
})
// With all the assets obtained, we'll now do a second query to
// obtain all the witnesses we know of for each asset.
assetWitnesses, err := fetchAssetWitnesses(ctx, q, assetIDs)
if err != nil {
return nil, nil, fmt.Errorf("unable to fetch asset "+
"witnesses: %w", err)
}
return dbAssets, assetWitnesses, nil
}
// AssetQueryFilters is a wrapper struct over the CommitmentConstraints struct
// which lets us filter the results of the set of assets returned.
type AssetQueryFilters struct {
tapfreighter.CommitmentConstraints
// MinAnchorHeight is the minimum block height the asset's anchor tx
// must have been confirmed at.
MinAnchorHeight int32
}
// QueryBalancesByAsset queries the balances for assets or alternatively
// for a selected one that matches the passed asset ID filter.
func (a *AssetStore) QueryBalancesByAsset(ctx context.Context,
assetID *asset.ID) (map[asset.ID]AssetBalance, error) {
var assetFilter []byte
if assetID != nil {
assetFilter = assetID[:]
}
balances := make(map[asset.ID]AssetBalance)
readOpts := NewAssetStoreReadTx()
dbErr := a.db.ExecTx(ctx, &readOpts, func(q ActiveAssetsStore) error {
dbBalances, err := q.QueryAssetBalancesByAsset(ctx, assetFilter)
if err != nil {
return fmt.Errorf("unable to query asset "+
"balances by asset: %w", err)
}
for _, assetBalance := range dbBalances {
var assetID asset.ID
copy(assetID[:], assetBalance.AssetID[:])
assetIDBalance := AssetBalance{
Balance: uint64(assetBalance.Balance),
Tag: assetBalance.AssetTag,
Type: asset.Type(assetBalance.AssetType),
OutputIndex: uint32(assetBalance.OutputIndex),
}
err = readOutPoint(
bytes.NewReader(assetBalance.GenesisPoint),
0, 0, &assetIDBalance.GenesisPoint,
)
if err != nil {
return err
}
copy(assetIDBalance.ID[:], assetBalance.AssetID)
copy(assetIDBalance.MetaHash[:], assetBalance.MetaHash)
balances[assetID] = assetIDBalance
}
return err
})
if dbErr != nil {
return nil, dbErr
}
return balances, nil
}
// QueryAssetBalancesByGroup queries the asset balances for asset groups or
// alternatively for a selected one that matches the passed filter.
func (a *AssetStore) QueryAssetBalancesByGroup(ctx context.Context,
groupKey *btcec.PublicKey) (map[asset.SerializedKey]AssetGroupBalance,
error) {
var groupFilter []byte
if groupKey != nil {
groupKeySerialized := groupKey.SerializeCompressed()
groupFilter = groupKeySerialized[:]
}
balances := make(map[asset.SerializedKey]AssetGroupBalance)
readOpts := NewAssetStoreReadTx()
dbErr := a.db.ExecTx(ctx, &readOpts, func(q ActiveAssetsStore) error {
dbBalances, err := q.QueryAssetBalancesByGroup(ctx, groupFilter)
if err != nil {
return fmt.Errorf("unable to query asset "+
"balances by asset: %w", err)
}
for _, groupBalance := range dbBalances {
var groupKey *btcec.PublicKey
if groupBalance.TweakedGroupKey != nil {
groupKey, err = btcec.ParsePubKey(
groupBalance.TweakedGroupKey,
)
if err != nil {
return err
}
}
serializedKey := asset.ToSerialized(groupKey)
balances[serializedKey] = AssetGroupBalance{
GroupKey: groupKey,
Balance: uint64(groupBalance.Balance),
}
}
return err
})
if dbErr != nil {
return nil, dbErr
}
return balances, nil
}
// FetchGroupedAssets fetches the set of assets with non-nil group keys.
func (a *AssetStore) FetchGroupedAssets(ctx context.Context) (