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
14 changes: 9 additions & 5 deletions cmd/commands/assets.go
Original file line number Diff line number Diff line change
Expand Up @@ -698,12 +698,16 @@ func listBatches(ctx *cli.Context) error {
}
}

resp, err := client.ListBatches(ctxc, &mintrpc.ListBatchRequest{
Filter: &mintrpc.ListBatchRequest_BatchKey{
BatchKey: batchKey,
},
listReq := &mintrpc.ListBatchRequest{
Verbose: ctx.Bool("verbose"),
})
}
if len(batchKey) > 0 {
listReq.Filter = &mintrpc.ListBatchRequest_BatchKey{
BatchKey: batchKey,
}
}

resp, err := client.ListBatches(ctxc, listReq)
if err != nil {
return fmt.Errorf("unable to list batches: %w", err)
}
Expand Down
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 @@ -111,6 +111,12 @@
fixes several bugs that could leave minting batches in an inconsistent
state in the case of a funding, database write, or restart error.

* [PR#2137](https://github.com/lightninglabs/taproot-assets/pull/2137)
fixes `ListBatches` REST `batch_key` handling so malformed path values
no longer silently return empty results. The endpoint now uses
`batch_key_str` in the REST path and returns `InvalidArgument` for
malformed or empty batch keys.

# New Features

## Functional Enhancements
Expand Down
109 changes: 109 additions & 0 deletions itest/assets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,15 @@ import (
"bytes"
"context"
"crypto/tls"
"encoding/base64"
"encoding/hex"
"io"
"net/http"
"net/url"
"os"
"slices"
"strings"
"testing"
"time"

"github.com/btcsuite/btcd/btcec/v2"
Expand Down Expand Up @@ -226,6 +232,109 @@ func testMintBatchResume(t *harnessTest) {
)
}

// testMintListBatchesRESTBatchKeyEncoding tests that the mint batches REST
// endpoint rejects malformed path batch keys with a client error.
func testMintListBatchesRESTBatchKeyEncoding(t *harnessTest) {
ctx := context.Background()

BuildMintingBatch(t.t, t.tapd, simpleAssets)

batchResp, err := t.tapd.ListBatches(ctx, &mintrpc.ListBatchRequest{})
require.NoError(t.t, err)
require.NotEmpty(t.t, batchResp.Batches)

batchKey := batchResp.Batches[0].Batch.BatchKey
require.NotEmpty(t.t, batchKey)
macaroonBytes, err := os.ReadFile(t.tapd.macPath)
require.NoError(t.t, err)

macaroonHex := hex.EncodeToString(macaroonBytes)

urlPrefix := "https://" + t.tapd.restListenAddr +
"/v1/taproot-assets/assets/mint/batches"

testCases := []struct {
name string
batchKey string
statusCode []int
}{
{
name: "valid_hex",
batchKey: hex.EncodeToString(batchKey),
statusCode: []int{http.StatusOK},
},
{
name: "invalid_std_base64_padded",
batchKey: base64.StdEncoding.EncodeToString(
batchKey,
),
statusCode: []int{
http.StatusBadRequest,
http.StatusNotFound,
http.StatusInternalServerError,
},
},
{
name: "invalid_url_base64_padded",
batchKey: base64.URLEncoding.EncodeToString(
batchKey,
),
statusCode: []int{
http.StatusBadRequest,
http.StatusInternalServerError,
},
},
{
name: "invalid_url_base64_raw",
batchKey: base64.RawURLEncoding.EncodeToString(
batchKey,
),
statusCode: []int{
http.StatusBadRequest,
http.StatusInternalServerError,
},
},
}

for _, testCase := range testCases {
t.t.Run(testCase.name, func(tt *testing.T) {
escapedBatchKey := url.PathEscape(testCase.batchKey)
reqURL := urlPrefix + "/" + escapedBatchKey
req, err := http.NewRequest(http.MethodGet, reqURL, nil)
require.NoError(tt, err)

req.Header.Set("Grpc-Metadata-macaroon", macaroonHex)

resp, err := client.Do(req)
require.NoError(tt, err)
tt.Cleanup(func() {
_ = resp.Body.Close()
})

require.Contains(
tt, testCase.statusCode, resp.StatusCode,
)

body, err := io.ReadAll(resp.Body)
require.NoError(tt, err)

if resp.StatusCode == http.StatusOK {
require.Contains(
tt, string(body), "\"batches\"",
)
return
}

if resp.StatusCode == http.StatusNotFound {
require.Contains(tt, string(body), "Not Found")
return
}

require.Contains(tt, string(body), "invalid batch_key")
})
}
}

// transferAssetProofs locates and exports the proof files for all given assets
// from the source node and imports them into the destination node.
func transferAssetProofs(t *harnessTest, src, dst *tapdHarness,
Expand Down
4 changes: 2 additions & 2 deletions itest/backup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ func testBackupRestoreGenesis(t *harnessTest) {
// imported (both anchor outpoints spent in step 7)
func testBackupRestoreTransferred(t *harnessTest) {
ctxb := context.Background()
ctxt, cancel := context.WithTimeout(ctxb, defaultWaitTimeout*4)
ctxt, cancel := context.WithTimeout(ctxb, defaultWaitTimeout)
Comment thread
sergey3bv marked this conversation as resolved.
defer cancel()

// === Stage 1: Mint 2 assets on Alice in separate batches ===
Expand Down Expand Up @@ -475,7 +475,7 @@ func assertAssetsMatch(t *harnessTest, expected []*taprpc.Asset,
// 5. Verify asset counts and group key presence on both nodes
func testBackupRestoreGrouped(t *harnessTest) {
ctxb := context.Background()
ctxt, cancel := context.WithTimeout(ctxb, defaultWaitTimeout*4)
ctxt, cancel := context.WithTimeout(ctxb, defaultWaitTimeout)
defer cancel()

// Mint a grouped asset and an ungrouped asset together.
Expand Down
4 changes: 4 additions & 0 deletions itest/test_list_on_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ var allTestCases = []*testCase{
name: "mint batch resume",
test: testMintBatchResume,
},
{
name: "mint list batches REST batch key encoding",
test: testMintListBatchesRESTBatchKeyEncoding,
},
{
name: "mint batch and transfer",
test: testMintBatchAndTransfer,
Expand Down
34 changes: 22 additions & 12 deletions rpcserver/rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -1032,27 +1032,37 @@ func (r *RPCServer) ListBatches(_ context.Context,
err error
)

switch {
case len(req.GetBatchKey()) > 0 && len(req.GetBatchKeyStr()) > 0:
return nil, fmt.Errorf("cannot specify both batch_key and " +
"batch_key_string")
switch filter := req.GetFilter().(type) {
// No filter specified.
case nil:

case *mintrpc.ListBatchRequest_BatchKey:
// Legacy clients (e.g. tapcli) set this oneof with nil or empty
// bytes to mean "list all"; treat that like an unset filter.
if len(filter.BatchKey) == 0 {
break
}

case len(req.GetBatchKey()) > 0:
batchKey, err = btcec.ParsePubKey(req.GetBatchKey())
batchKey, err = btcec.ParsePubKey(filter.BatchKey)
if err != nil {
return nil, fmt.Errorf("invalid batch key: %w", err)
return nil, fmt.Errorf("invalid batch_key: %w", err)
}

case len(req.GetBatchKeyStr()) > 0:
batchKeyBytes, err := hex.DecodeString(req.GetBatchKeyStr())
case *mintrpc.ListBatchRequest_BatchKeyStr:
// Same as BatchKey: empty means list all for backward
// compatibility with callers that always set the oneof arm.
if filter.BatchKeyStr == "" {
break
}

batchKeyBytes, err := hex.DecodeString(filter.BatchKeyStr)
if err != nil {
return nil, fmt.Errorf("invalid batch key string: %w",
err)
return nil, fmt.Errorf("invalid batch_key: %w", err)
}

batchKey, err = btcec.ParsePubKey(batchKeyBytes)
if err != nil {
return nil, fmt.Errorf("invalid batch key: %w", err)
return nil, fmt.Errorf("invalid batch_key: %w", err)
}
}

Expand Down
36 changes: 18 additions & 18 deletions taprpc/mintrpc/mint.pb.gw.go

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

16 changes: 8 additions & 8 deletions taprpc/mintrpc/mint.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
]
}
},
"/v1/taproot-assets/assets/mint/batches/{batch_key}": {
"/v1/taproot-assets/assets/mint/batches/{batch_key_str}": {
"get": {
"summary": "tapcli: `assets mint batches`\nListBatches lists the set of batches submitted to the daemon, including\npending and cancelled batches.",
"operationId": "Mint_ListBatches",
Expand All @@ -69,19 +69,19 @@
},
"parameters": [
{
"name": "batch_key",
"description": "The optional batch key of the batch to list, specified as raw bytes\n(gRPC only).",
"name": "batch_key_str",
"description": "The optional batch key of the batch to list, specified as a hex\nencoded string (use this for REST).",
"in": "path",
"required": true,
"type": "string",
"format": "byte"
"type": "string"
},
{
"name": "batch_key_str",
"description": "The optional batch key of the batch to list, specified as a hex\nencoded string (use this for REST).",
"name": "batch_key",
"description": "The optional batch key of the batch to list, specified as raw bytes\n(gRPC only).",
"in": "query",
"required": false,
"type": "string"
"type": "string",
"format": "byte"
},
{
"name": "verbose",
Expand Down
2 changes: 1 addition & 1 deletion taprpc/mintrpc/mint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ http:
body: "*"

- selector: mintrpc.Mint.ListBatches
get: "/v1/taproot-assets/assets/mint/batches/{batch_key}"
get: "/v1/taproot-assets/assets/mint/batches/{batch_key_str}"

- selector: mintrpc.Mint.SubscribeMintEvents
post: "/v1/taproot-assets/events/asset-mint"
Expand Down
Loading