Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/release-notes/release-notes-0.8.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,12 @@
clients to distinguish grouped fungible burns from grouped collectible
burns.

- [PR#2130](https://github.com/lightninglabs/taproot-assets/pull/2130)
Add `asset_genesis` and `decimal_display` fields to `AssetGroupBalance`
in `ListBalances`. When using `group_by=group_key` mode, clients now
receive asset metadata (name, type, decimal display) alongside grouped
balances without requiring additional RPC calls.

- [PR#2100](https://github.com/lightninglabs/taproot-assets/pull/2100)
Add pagination support (offset, limit, direction) to the `AssetLeaves`
RPC endpoint, and add `MaxPageSize` validation to `AssetRoots`.
Expand Down
26 changes: 25 additions & 1 deletion itest/multi_asset_group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,40 @@ func testMintMultiAssetGroups(t *harnessTest) {
require.NoError(t.t, err)

// For each group minted, we check that the total balance for each
// group matches our minting requests.
// group matches our minting requests, and that genesis info is
// populated correctly.
var singleAssetGroupKey, normalGroupKey, collectGroupKey string
for groupKey, groupBalance := range balancesResp.AssetGroupBalances {
// Verify genesis info is populated for all group balances.
require.NotNil(t.t, groupBalance.AssetGenesis,
"group %s missing genesis info", groupKey)
require.NotEmpty(t.t, groupBalance.AssetGenesis.Name,
"group %s missing asset name", groupKey)
require.NotEmpty(t.t, groupBalance.AssetGenesis.AssetId,
"group %s missing asset ID", groupKey)
require.NotEmpty(t.t, groupBalance.AssetGenesis.GenesisPoint,
"group %s missing genesis point", groupKey)

switch groupBalance.Balance {
case issuableAsset.Asset.Amount:
singleAssetGroupKey = groupKey

case normalGroupSum:
normalGroupKey = groupKey

// Verify the normal group has NORMAL asset type.
require.Equal(t.t, taprpc.AssetType_NORMAL,
groupBalance.AssetGenesis.AssetType,
"normal group has wrong asset type")

case collectGroupSum:
collectGroupKey = groupKey

// Verify the collectible group has COLLECTIBLE type.
require.Equal(t.t, taprpc.AssetType_COLLECTIBLE,
groupBalance.AssetGenesis.AssetType,
"collectible group has wrong asset type")

default:
t.t.Fatalf("minted group %v has unexpected balance %v",
groupKey, groupBalance.Balance)
Expand Down
43 changes: 40 additions & 3 deletions rpcserver/rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -1483,11 +1483,48 @@ func (r *RPCServer) listBalancesByGroupKey(ctx context.Context,
groupKey = balance.GroupKey.SerializeCompressed()
}

// Create the genesis info for the representative issuance.
genesisInfo := &taprpc.GenesisInfo{
GenesisPoint: balance.GenesisPoint.String(),
AssetType: taprpc.AssetType(balance.Type),
Name: balance.Tag,
MetaHash: balance.MetaHash[:],
AssetId: balance.ID[:],
OutputIndex: balance.OutputIndex,
}

groupKeyString := hex.EncodeToString(groupKey)
resp.AssetGroupBalances[groupKeyString] = &taprpc.AssetGroupBalance{
GroupKey: groupKey,
Balance: balance.Balance,
groupBalance := &taprpc.AssetGroupBalance{
GroupKey: groupKey,
Balance: balance.Balance,
AssetGenesis: genesisInfo,
}

// Fetch the decimal display for this asset ID.
decDisplay, err := r.cfg.AddrBook.DecDisplayForAssetID(
ctx, balance.ID,
)
Comment thread
kaldun-tech marked this conversation as resolved.
switch {
case errors.Is(err, address.ErrAssetMetaNotFound):
// Asset legitimately has no meta — leave DecimalDisplay
// unset.

case err != nil:
return nil, fmt.Errorf("unable to fetch decimal "+
"display for asset %x: %w", balance.ID[:], err)

default:
groupBalance.DecimalDisplay = fn.MapOptionZ(
decDisplay,
func(d uint32) *taprpc.DecimalDisplay {
return &taprpc.DecimalDisplay{
DecimalDisplay: d,
}
},
)
}

resp.AssetGroupBalances[groupKeyString] = groupBalance
}

// We will also report the number of unconfirmed transfers. This is
Expand Down
40 changes: 34 additions & 6 deletions tapdb/assets_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,11 +412,20 @@ type AssetBalance struct {
GroupKey []byte
}

// AssetGroupBalance holds abalance query result for a particular asset group
// or all asset groups tracked by this daemon.
// AssetGroupBalance holds a balance query result for a particular asset group
// or all asset groups tracked by this daemon. It includes the genesis info of
// a representative issuance in the group.
type AssetGroupBalance struct {
GroupKey *btcec.PublicKey
Balance uint64

// Genesis info fields for the representative issuance.
ID asset.ID
Tag string
MetaHash [asset.MetaHashLen]byte
Type asset.Type
GenesisPoint wire.OutPoint
OutputIndex uint32
}

// cacheableTimestamp is a wrapper around an int32 that can be used as a
Expand Down Expand Up @@ -1219,11 +1228,30 @@ func (a *AssetStore) QueryAssetBalancesByGroup(ctx context.Context,
}
}

serializedKey := asset.ToSerialized(groupKey)
balances[serializedKey] = AssetGroupBalance{
GroupKey: groupKey,
Balance: uint64(groupBalance.Balance),
assetGroupBalance := AssetGroupBalance{
GroupKey: groupKey,
Balance: uint64(groupBalance.Balance),
Tag: groupBalance.AssetTag,
Type: asset.Type(groupBalance.AssetType),
OutputIndex: uint32(groupBalance.OutputIndex),
}

// Parse the genesis point from the raw bytes.
err = readOutPoint(
bytes.NewReader(groupBalance.PrevOut),
0, 0, &assetGroupBalance.GenesisPoint,
)
if err != nil {
return err
}

// Copy the asset ID and meta hash.
copy(assetGroupBalance.ID[:], groupBalance.AssetID)
copy(assetGroupBalance.MetaHash[:],
groupBalance.MetaHash)

serializedKey := asset.ToSerialized(groupKey)
balances[serializedKey] = assetGroupBalance
}

return err
Expand Down
47 changes: 46 additions & 1 deletion tapdb/assets_store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3111,7 +3111,7 @@ func TestQueryAssetBalances(t *testing.T) {
amt: 4,
},
}
assetGen.genAssets(t, assetsStore, assetDesc)
genAssets, _ := assetGen.genAssets(t, assetsStore, assetDesc)

// Loop through assetDesc and sum the amt values
totalBalances := uint64(0)
Expand Down Expand Up @@ -3145,6 +3145,51 @@ func TestQueryAssetBalances(t *testing.T) {
}
require.Equal(t, totalGroupedBalances, balanceByGroupSum)

// Verify that the new genesis info fields are populated correctly for
// each group balance. The genesis info should match the group anchor
// genesis (the first asset minted in each group).
//
// Build expected maps using the actual tweaked group keys from the
// generated assets (not the raw internal keys from assetGen.groupKeys).
// Assets 0 and 1 are the anchors for groups 0 and 1 respectively.
group0Key := asset.ToSerialized(&genAssets[0].GroupKey.GroupPubKey)
group1Key := asset.ToSerialized(&genAssets[1].GroupKey.GroupPubKey)

expectedAnchorGenesis := map[asset.SerializedKey]asset.Genesis{
group0Key: genAssets[0].Genesis,
group1Key: genAssets[1].Genesis,
}
expectedAnchorPoints := map[asset.SerializedKey]wire.OutPoint{
group0Key: assetGen.anchorPoints[0],
group1Key: assetGen.anchorPoints[0],
}

for groupKey, balance := range balancesByGroup {
// Verify genesis info fields are populated.
require.NotEqual(t, asset.ID{}, balance.ID,
"group %x missing asset ID", groupKey)
require.NotEmpty(t, balance.Tag,
"group %x missing asset tag", groupKey)
require.NotEqual(t, wire.OutPoint{}, balance.GenesisPoint,
"group %x missing genesis point", groupKey)

// Verify the genesis info matches the expected anchor genesis.
expectedGen := expectedAnchorGenesis[groupKey]
require.Equal(t, expectedGen.ID(), balance.ID,
"group %x has wrong asset ID", groupKey)
require.Equal(t, expectedGen.Tag, balance.Tag,
"group %x has wrong asset tag", groupKey)
require.Equal(t, expectedGen.Type, balance.Type,
"group %x has wrong asset type", groupKey)
require.Equal(t, expectedGen.OutputIndex, balance.OutputIndex,
"group %x has wrong output index", groupKey)

// Verify the genesis point matches the expected anchor point.
expectedPoint := expectedAnchorPoints[groupKey]
require.Equal(t, expectedPoint, balance.GenesisPoint,
"group %x has wrong genesis point", groupKey)
}

// Now we lease the first asset for 1 hour. This will cause the second
// one also to be leased, since it's on the same anchor transaction. The
// second asset is in its own group, so when leased, the entire group is
Expand Down
58 changes: 51 additions & 7 deletions tapdb/sqlc/assets.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading