Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ListBurns RPC #1178

Merged
merged 6 commits into from
Nov 26, 2024
Merged
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
67 changes: 67 additions & 0 deletions cmd/tapcli/assets.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ var assetsCommands = []cli.Command{
listAssetBalancesCommand,
sendAssetsCommand,
burnAssetsCommand,
listBurnsCommand,
listTransfersCommand,
fetchMetaCommand,
},
Expand All @@ -52,6 +53,7 @@ var (
assetShowUnconfMintsName = "show_unconfirmed_mints"
assetGroupKeyName = "group_key"
assetGroupAnchorName = "group_anchor"
anchorTxidName = "anchor_txid"
batchKeyName = "batch_key"
groupByGroupName = "by_group"
assetIDName = "asset_id"
Expand Down Expand Up @@ -858,6 +860,71 @@ func burnAssets(ctx *cli.Context) error {
return nil
}

var listBurnsCommand = cli.Command{
Name: "listburns",
Usage: "list burnt assets",
Description: `
List assets that have been burned by this daemon. These are assets that
have been destroyed and are no longer spendable.

Some filters may be used to return more specific results.
`,
Flags: []cli.Flag{
cli.StringFlag{
Name: assetIDName,
Usage: "the asset ID of the burnt asset",
},
cli.StringFlag{
Name: assetGroupKeyName,
Usage: "the group key of the burnt asset",
},
cli.StringFlag{
Name: anchorTxidName,
Usage: "the txid of the transaction the burn was " +
"anchored to",
},
},
Action: listBurns,
}

func listBurns(ctx *cli.Context) error {
assetIDHex := ctx.String(assetIDName)
assetIDBytes, err := hex.DecodeString(assetIDHex)
if err != nil {
return fmt.Errorf("invalid asset ID: %w", err)
}

groupKeyHex := ctx.String(assetGroupKeyName)
groupKeyBytes, err := hex.DecodeString(groupKeyHex)
if err != nil {
return fmt.Errorf("invalid group key: %w", err)
}

anchorTxidStr := ctx.String(anchorTxidName)
anchorTxid, err := hex.DecodeString(anchorTxidStr)
if err != nil {
return fmt.Errorf("invalid anchor txid: %w", err)
}

ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()

resp, err := client.ListBurns(
ctxc, &taprpc.ListBurnsRequest{
AssetId: assetIDBytes,
TweakedGroupKey: groupKeyBytes,
AnchorTxid: anchorTxid,
},
)
if err != nil {
return fmt.Errorf("could not list burns: %w", err)
}

printRespJSON(resp)
return nil
}

var listTransfersCommand = cli.Command{
Name: "transfers",
ShortName: "t",
Expand Down
91 changes: 90 additions & 1 deletion itest/burn_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package itest

import (
"bytes"
"context"
"encoding/hex"

Expand Down Expand Up @@ -129,12 +130,17 @@ func testBurnAssets(t *harnessTest) {
// Test case 2: We'll now try to burn a small amount of assets, which
// should select the largest output, which is located alone in an anchor
// output.
const burnAmt = 100
const (
burnAmt = 100
burnNote = "blazeit"
)

burnResp, err := t.tapd.BurnAsset(ctxt, &taprpc.BurnAssetRequest{
Asset: &taprpc.BurnAssetRequest_AssetId{
AssetId: simpleAssetID[:],
},
AmountToBurn: burnAmt,
Note: burnNote,
ConfirmationText: taprootassets.AssetBurnConfirmationText,
})
require.NoError(t.t, err)
Expand Down Expand Up @@ -169,6 +175,16 @@ func testBurnAssets(t *harnessTest) {
t.t, t.tapd, simpleAssetGen.AssetId, simpleAsset.Amount-burnAmt,
)

burns, err := t.tapd.ListBurns(ctxt, &taprpc.ListBurnsRequest{})
require.NoError(t.t, err)

require.Len(t.t, burns.Burns, 1)
burn := burns.Burns[0]
require.Equal(t.t, uint64(burnAmt), burn.Amount)
require.Equal(t.t, burnResp.BurnTransfer.AnchorTxHash, burn.AnchorTxid)
require.Equal(t.t, burn.AssetId, simpleAssetID[:])
require.Equal(t.t, burn.Note, burnNote)

// The burned asset should be pruned from the tree when we next spend
// the anchor output it was in (together with the change). So let's test
// that we can successfully spend the change output.
Expand Down Expand Up @@ -280,6 +296,35 @@ func testBurnAssets(t *harnessTest) {
t.t, t.tapd, simpleGroupGen.AssetId, simpleGroup.Amount-burnAmt,
)

burns, err = t.tapd.ListBurns(ctxt, &taprpc.ListBurnsRequest{})
require.NoError(t.t, err)

require.Len(t.t, burns.Burns, 4)
var groupBurn *taprpc.AssetBurn
for _, b := range burns.Burns {
if bytes.Equal(b.AssetId, simpleGroupGen.AssetId) {
groupBurn = b
}
}

// Keep track of the txhash of the anchor transaction that completed
// this transfer. This will be used later to query burns with a txhash
// filter.
groupBurnTxHash := burnResp.BurnTransfer.AnchorTxHash

require.Equal(t.t, uint64(burnAmt), groupBurn.Amount)
require.Equal(
t.t, burnResp.BurnTransfer.AnchorTxHash, groupBurn.AnchorTxid,
)

require.Equal(t.t, groupBurn.AssetId, simpleGroupGen.AssetId[:])
require.Equal(
t.t, groupBurn.TweakedGroupKey,
simpleGroup.AssetGroup.TweakedGroupKey,
)

require.Equal(t.t, groupBurn.Note, "")

// Test case 6: Burn the single unit of a grouped collectible. We start
// by making sure we still have the full balance before burning.
AssertBalanceByID(
Expand All @@ -305,6 +350,36 @@ func testBurnAssets(t *harnessTest) {
simpleGroupCollectGen.AssetId, []uint64{1}, 6, 7, 1, true,
)
AssertBalanceByID(t.t, t.tapd, simpleGroupCollectGen.AssetId, 0)

// We now perform some queries to test the filters of the ListBurns
// call.

// Fetch the burns related to the simple asset id, which should have a
// total of 2 burns (tc1 & tc4).
burns, err = t.tapd.ListBurns(ctxt, &taprpc.ListBurnsRequest{
AssetId: simpleAssetGen.AssetId,
})
require.NoError(t.t, err)

require.Len(t.t, burns.Burns, 2)

// Fetch the burns related to the group key of the grouped asset in tc5.
// There should be 1 burn.
burns, err = t.tapd.ListBurns(ctxt, &taprpc.ListBurnsRequest{
TweakedGroupKey: simpleGroup.AssetGroup.TweakedGroupKey,
})
require.NoError(t.t, err)

require.Len(t.t, burns.Burns, 1)

// Fetch the burns associated with the txhash of the burn in tc5. There
// should be 1 burn returned.
burns, err = t.tapd.ListBurns(ctxt, &taprpc.ListBurnsRequest{
AnchorTxid: groupBurnTxHash,
})
require.NoError(t.t, err)

require.Len(t.t, burns.Burns, 1)
}

// testBurnGroupedAssets tests that some amount of an asset from an asset group
Expand All @@ -315,6 +390,7 @@ func testBurnGroupedAssets(t *harnessTest) {
miner = t.lndHarness.Miner().Client

firstMintReq = issuableAssets[0]
burnNote = "blazeit"
)

// We start off without any asset groups.
Expand Down Expand Up @@ -376,6 +452,7 @@ func testBurnGroupedAssets(t *harnessTest) {
AssetId: burnAssetID,
},
AmountToBurn: burnAmt,
Note: burnNote,
ConfirmationText: taprootassets.AssetBurnConfirmationText,
})
require.NoError(t.t, err)
Expand Down Expand Up @@ -414,4 +491,16 @@ func testBurnGroupedAssets(t *harnessTest) {
encodedGroupKey = hex.EncodeToString(assetGroupKey)
assetGroup = assetGroups.Groups[encodedGroupKey]
require.Len(t.t, assetGroup.Assets, 2)

burns, err := t.tapd.ListBurns(ctxb, &taprpc.ListBurnsRequest{
TweakedGroupKey: assetGroupKey,
})
require.NoError(t.t, err)
require.Len(t.t, burns.Burns, 1)

burn := burns.Burns[0]

require.Equal(t.t, burnAmt, burn.Amount)
require.Equal(t.t, burnNote, burn.Note)
require.Equal(t.t, assetGroupKey, burn.TweakedGroupKey)
}
4 changes: 4 additions & 0 deletions perms/perms.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ var (
Entity: "assets",
Action: "write",
}},
"/taprpc.TaprootAssets/ListBurns": {{
Entity: "assets",
Action: "read",
}},
"/taprpc.TaprootAssets/FetchAssetMeta": {{
Entity: "assets",
Action: "read",
Expand Down
39 changes: 37 additions & 2 deletions rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
"github.com/lightninglabs/taproot-assets/rfqmsg"
"github.com/lightninglabs/taproot-assets/rpcperms"
"github.com/lightninglabs/taproot-assets/tapchannel"
"github.com/lightninglabs/taproot-assets/tapdb"
"github.com/lightninglabs/taproot-assets/tapfreighter"
"github.com/lightninglabs/taproot-assets/tapgarden"
"github.com/lightninglabs/taproot-assets/tappsbt"
Expand Down Expand Up @@ -2275,7 +2276,7 @@ func (r *rpcServer) AnchorVirtualPsbts(ctx context.Context,
}

resp, err := r.cfg.ChainPorter.RequestShipment(
tapfreighter.NewPreSignedParcel(vPackets, inputCommitments),
tapfreighter.NewPreSignedParcel(vPackets, inputCommitments, ""),
)
if err != nil {
return nil, fmt.Errorf("error requesting delivery: %w", err)
Expand Down Expand Up @@ -3290,7 +3291,7 @@ func (r *rpcServer) BurnAsset(ctx context.Context,
resp, err := r.cfg.ChainPorter.RequestShipment(
tapfreighter.NewPreSignedParcel(
[]*tappsbt.VPacket{fundResp.VPacket},
fundResp.InputCommitments,
fundResp.InputCommitments, in.Note,
),
)
if err != nil {
Expand Down Expand Up @@ -3328,6 +3329,40 @@ func (r *rpcServer) BurnAsset(ctx context.Context,
}, nil
}

// ListBurns returns a list of burnt assets. Some filters may be defined in the
// request to return more specific results.
func (r *rpcServer) ListBurns(ctx context.Context,
in *taprpc.ListBurnsRequest) (*taprpc.ListBurnsResponse, error) {

burns, err := r.cfg.AssetStore.QueryBurns(
ctx, tapdb.QueryBurnsFilters{
AssetID: in.AssetId,
GroupKey: in.TweakedGroupKey,
AnchorTxid: in.AnchorTxid,
},
)
if err != nil {
return nil, err
}

rpcBurns := fn.Map(burns, marshalRpcBurn)

return &taprpc.ListBurnsResponse{
Burns: rpcBurns,
Roasbeef marked this conversation as resolved.
Show resolved Hide resolved
}, nil
}

// marshalRpcBurn creates an instance of *taprpc.AssetBurn from the tapdb model.
func marshalRpcBurn(b *tapfreighter.AssetBurn) *taprpc.AssetBurn {
return &taprpc.AssetBurn{
Note: b.Note,
AssetId: b.AssetID,
TweakedGroupKey: b.GroupKey,
Amount: b.Amount,
AnchorTxid: b.AnchorTxid[:],
}
}

// marshalOutboundParcel turns a pending parcel into its RPC counterpart.
func marshalOutboundParcel(
parcel *tapfreighter.OutboundParcel) (*taprpc.AssetTransfer,
Expand Down
Loading
Loading