Skip to content

Commit caa8e72

Browse files
authored
Merge pull request #1199 from itsrachelfish/listassets-filters
Updated ListAssetRequest to include additional filter options
2 parents db28a03 + ebd4f4a commit caa8e72

File tree

9 files changed

+1411
-1093
lines changed

9 files changed

+1411
-1093
lines changed

rpcserver.go

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,9 +1059,57 @@ func (r *rpcServer) ListAssets(ctx context.Context,
10591059
"and include_leased")
10601060
}
10611061

1062+
constraints := tapfreighter.CommitmentConstraints{
1063+
MinAmt: req.MinAmount,
1064+
MaxAmt: req.MaxAmount,
1065+
CoinSelectType: tapsend.DefaultCoinSelectType,
1066+
}
1067+
1068+
if len(req.GroupKey) > 0 {
1069+
groupKey, err := btcec.ParsePubKey(req.GroupKey)
1070+
if err != nil {
1071+
return nil, fmt.Errorf("error parsing group key: %w",
1072+
err)
1073+
}
1074+
1075+
constraints.AssetSpecifier = asset.NewSpecifierFromGroupKey(
1076+
*groupKey,
1077+
)
1078+
}
1079+
1080+
filters := &tapdb.AssetQueryFilters{
1081+
CommitmentConstraints: constraints,
1082+
}
1083+
1084+
if req.ScriptKey != nil {
1085+
scriptKey, err := taprpc.UnmarshalScriptKey(req.ScriptKey)
1086+
if err != nil {
1087+
return nil, fmt.Errorf("unable to decode script key: "+
1088+
"%w", err)
1089+
}
1090+
1091+
filters.ScriptKey = scriptKey
1092+
}
1093+
1094+
if req.AnchorOutpoint != nil {
1095+
txid, err := chainhash.NewHash(req.AnchorOutpoint.Txid)
1096+
if err != nil {
1097+
return nil, fmt.Errorf("error parsing outpoint: %w",
1098+
err)
1099+
}
1100+
outPoint := &wire.OutPoint{
1101+
Hash: *txid,
1102+
Index: req.AnchorOutpoint.OutputIndex,
1103+
}
1104+
1105+
filters.AnchorPoint = outPoint
1106+
}
1107+
10621108
rpcAssets, err := r.fetchRpcAssets(
10631109
ctx, req.WithWitness, req.IncludeSpent, req.IncludeLeased,
1110+
filters,
10641111
)
1112+
10651113
if err != nil {
10661114
return nil, err
10671115
}
@@ -1114,10 +1162,11 @@ func (r *rpcServer) ListAssets(ctx context.Context,
11141162
}
11151163

11161164
func (r *rpcServer) fetchRpcAssets(ctx context.Context, withWitness,
1117-
includeSpent, includeLeased bool) ([]*taprpc.Asset, error) {
1165+
includeSpent, includeLeased bool,
1166+
queryFilters *tapdb.AssetQueryFilters) ([]*taprpc.Asset, error) {
11181167

11191168
assets, err := r.cfg.AssetStore.FetchAllAssets(
1120-
ctx, includeSpent, includeLeased, nil,
1169+
ctx, includeSpent, includeLeased, queryFilters,
11211170
)
11221171
if err != nil {
11231172
return nil, fmt.Errorf("unable to read chain assets: %w", err)
@@ -1243,7 +1292,9 @@ func (r *rpcServer) listBalancesByGroupKey(ctx context.Context,
12431292
func (r *rpcServer) ListUtxos(ctx context.Context,
12441293
req *taprpc.ListUtxosRequest) (*taprpc.ListUtxosResponse, error) {
12451294

1246-
rpcAssets, err := r.fetchRpcAssets(ctx, false, false, req.IncludeLeased)
1295+
rpcAssets, err := r.fetchRpcAssets(
1296+
ctx, false, false, req.IncludeLeased, nil,
1297+
)
12471298
if err != nil {
12481299
return nil, err
12491300
}

tapdb/assets_store.go

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -858,7 +858,7 @@ func (a *AssetStore) dbAssetsToChainAssets(dbAssets []ConfirmedAsset,
858858
// constraintsToDbFilter maps application level constraints to the set of
859859
// filters we use in the SQL queries.
860860
func (a *AssetStore) constraintsToDbFilter(
861-
query *AssetQueryFilters) QueryAssetFilters {
861+
query *AssetQueryFilters) (QueryAssetFilters, error) {
862862

863863
assetFilter := QueryAssetFilters{
864864
Now: sql.NullTime{
@@ -868,17 +868,36 @@ func (a *AssetStore) constraintsToDbFilter(
868868
}
869869
if query != nil {
870870
if query.MinAmt != 0 {
871-
assetFilter.MinAmt = sql.NullInt64{
872-
Int64: int64(query.MinAmt),
873-
Valid: true,
874-
}
871+
assetFilter.MinAmt = sqlInt64(query.MinAmt)
872+
}
873+
874+
if query.MaxAmt != 0 {
875+
assetFilter.MaxAmt = sqlInt64(query.MaxAmt)
875876
}
877+
876878
if query.MinAnchorHeight != 0 {
877879
assetFilter.MinAnchorHeight = sqlInt32(
878880
query.MinAnchorHeight,
879881
)
880882
}
881883

884+
if query.ScriptKey != nil {
885+
key := query.ScriptKey.PubKey
886+
assetFilter.TweakedScriptKey = key.SerializeCompressed()
887+
}
888+
889+
if query.AnchorPoint != nil {
890+
anchorPointBytes, err := encodeOutpoint(
891+
*query.AnchorPoint,
892+
)
893+
if err != nil {
894+
return QueryAssetFilters{}, fmt.Errorf(
895+
"unable to encode outpoint: %w", err)
896+
}
897+
898+
assetFilter.AnchorPoint = anchorPointBytes
899+
}
900+
882901
// Add asset ID bytes and group key bytes to the filter. These
883902
// byte arrays are empty if the asset ID or group key is not
884903
// specified in the query.
@@ -903,7 +922,7 @@ func (a *AssetStore) constraintsToDbFilter(
903922
}
904923
}
905924

906-
return assetFilter
925+
return assetFilter, nil
907926
}
908927

909928
// specificAssetFilter maps the given asset parameters to the set of filters
@@ -975,6 +994,13 @@ type AssetQueryFilters struct {
975994
// MinAnchorHeight is the minimum block height the asset's anchor tx
976995
// must have been confirmed at.
977996
MinAnchorHeight int32
997+
998+
// ScriptKey allows filtering by asset script key.
999+
ScriptKey *asset.ScriptKey
1000+
1001+
// AnchorPoint allows filtering by the outpoint the asset is anchored
1002+
// to.
1003+
AnchorPoint *wire.OutPoint
9781004
}
9791005

9801006
// QueryBalancesByAsset queries the balances for assets or alternatively
@@ -1194,7 +1220,10 @@ func (a *AssetStore) FetchAllAssets(ctx context.Context, includeSpent,
11941220

11951221
// We'll now map the application level filtering to the type of
11961222
// filtering our database query understands.
1197-
assetFilter := a.constraintsToDbFilter(query)
1223+
assetFilter, err := a.constraintsToDbFilter(query)
1224+
if err != nil {
1225+
return nil, err
1226+
}
11981227

11991228
// By default, the spent boolean is null, which means we'll fetch all
12001229
// assets. Only if we should exclude spent assets, we'll set the spent
@@ -1995,9 +2024,12 @@ func (a *AssetStore) ListEligibleCoins(ctx context.Context,
19952024

19962025
// First, we'll map the commitment constraints to our database query
19972026
// filters.
1998-
assetFilter := a.constraintsToDbFilter(&AssetQueryFilters{
2027+
assetFilter, err := a.constraintsToDbFilter(&AssetQueryFilters{
19992028
CommitmentConstraints: constraints,
20002029
})
2030+
if err != nil {
2031+
return nil, err
2032+
}
20012033

20022034
// We only want to select unspent and non-leased commitments.
20032035
assetFilter.Spent = sqlBool(false)

tapdb/assets_store_test.go

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,12 @@ func filterMinAmt(amt uint64) filterOpt {
724724
}
725725
}
726726

727+
func filterMaxAmt(amt uint64) filterOpt {
728+
return func(f *AssetQueryFilters) {
729+
f.MaxAmt = amt
730+
}
731+
}
732+
727733
func filterCoinSelectType(typ tapsend.CoinSelectType) filterOpt {
728734
return func(f *AssetQueryFilters) {
729735
f.CoinSelectType = typ
@@ -742,6 +748,18 @@ func filterAnchorHeight(height int32) filterOpt {
742748
}
743749
}
744750

751+
func filterAnchorPoint(point *wire.OutPoint) filterOpt {
752+
return func(f *AssetQueryFilters) {
753+
f.AnchorPoint = point
754+
}
755+
}
756+
757+
func filterScriptKey(key *asset.ScriptKey) filterOpt {
758+
return func(f *AssetQueryFilters) {
759+
f.ScriptKey = key
760+
}
761+
}
762+
745763
// TestFetchAllAssets tests that the different AssetQueryFilters work as
746764
// expected.
747765
func TestFetchAllAssets(t *testing.T) {
@@ -780,6 +798,7 @@ func TestFetchAllAssets(t *testing.T) {
780798
assetGen: assetGen.assetGens[2],
781799
anchorPoint: assetGen.anchorPoints[0],
782800
amt: 34,
801+
scriptKey: scriptKeyWithScript,
783802
}, {
784803
assetGen: assetGen.assetGens[3],
785804
anchorPoint: assetGen.anchorPoints[1],
@@ -898,6 +917,38 @@ func TestFetchAllAssets(t *testing.T) {
898917
includeLeased: true,
899918
includeSpent: true,
900919
numAssets: 8,
920+
}, {
921+
name: "max amount",
922+
filter: makeFilter(
923+
filterMaxAmt(100),
924+
filterCoinSelectType(tapsend.ScriptTreesAllowed),
925+
),
926+
numAssets: 6,
927+
}, {
928+
name: "max amount, include spent",
929+
filter: makeFilter(
930+
filterMaxAmt(100),
931+
filterCoinSelectType(tapsend.ScriptTreesAllowed),
932+
),
933+
includeSpent: true,
934+
numAssets: 7,
935+
}, {
936+
name: "max amount, include leased",
937+
filter: makeFilter(
938+
filterMaxAmt(100),
939+
filterCoinSelectType(tapsend.ScriptTreesAllowed),
940+
),
941+
includeLeased: true,
942+
numAssets: 8,
943+
}, {
944+
name: "max amount, include leased, include spent",
945+
filter: makeFilter(
946+
filterMaxAmt(100),
947+
filterCoinSelectType(tapsend.ScriptTreesAllowed),
948+
),
949+
includeLeased: true,
950+
includeSpent: true,
951+
numAssets: 9,
901952
}, {
902953
name: "default min height, include spent",
903954
filter: makeFilter(
@@ -914,7 +965,7 @@ func TestFetchAllAssets(t *testing.T) {
914965
),
915966
numAssets: 0,
916967
}, {
917-
name: "default min height, include spent",
968+
name: "specific height, include spent",
918969
filter: makeFilter(
919970
filterAnchorHeight(502),
920971
filterCoinSelectType(tapsend.ScriptTreesAllowed),
@@ -928,6 +979,21 @@ func TestFetchAllAssets(t *testing.T) {
928979
filterCoinSelectType(tapsend.Bip86Only),
929980
),
930981
numAssets: 0,
982+
}, {
983+
name: "query by script key",
984+
filter: makeFilter(
985+
filterScriptKey(scriptKeyWithScript),
986+
filterCoinSelectType(tapsend.ScriptTreesAllowed),
987+
),
988+
numAssets: 1,
989+
}, {
990+
name: "query by script key, include leased",
991+
filter: makeFilter(
992+
filterScriptKey(scriptKeyWithScript),
993+
filterCoinSelectType(tapsend.ScriptTreesAllowed),
994+
),
995+
includeLeased: true,
996+
numAssets: 2,
931997
}, {
932998
name: "query by group key only",
933999
filter: makeFilter(
@@ -952,6 +1018,13 @@ func TestFetchAllAssets(t *testing.T) {
9521018
)), filterDistinctSpecifier(),
9531019
),
9541020
numAssets: 2,
1021+
}, {
1022+
name: "query by anchor point",
1023+
filter: makeFilter(
1024+
filterAnchorPoint(&assetGen.anchorPoints[0]),
1025+
filterCoinSelectType(tapsend.ScriptTreesAllowed),
1026+
),
1027+
numAssets: 3,
9551028
}}
9561029

9571030
for _, tc := range testCases {

tapdb/sqlc/assets.sql.go

Lines changed: 10 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tapdb/sqlc/queries/assets.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,7 @@ JOIN chain_txns txns
498498
-- specified.
499499
WHERE (
500500
assets.amount >= COALESCE(sqlc.narg('min_amt'), assets.amount) AND
501+
assets.amount <= COALESCE(sqlc.narg('max_amt'), assets.amount) AND
501502
assets.spent = COALESCE(sqlc.narg('spent'), assets.spent) AND
502503
(key_group_info_view.tweaked_group_key = sqlc.narg('key_group_filter') OR
503504
sqlc.narg('key_group_filter') IS NULL) AND

tapfreighter/interface.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ type CommitmentConstraints struct {
3939
// to satisfy the constraints.
4040
MinAmt uint64
4141

42+
// MaxAmt specifies the maximum amount that an asset commitment needs to
43+
// hold to satisfy the constraints.
44+
MaxAmt uint64
45+
4246
// PrevIDs are the set of inputs allowed to be used.
4347
PrevIDs []asset.PrevID
4448

0 commit comments

Comments
 (0)