@@ -2235,8 +2235,7 @@ func (r *rpcServer) FundVirtualPsbt(ctx context.Context,
2235
2235
// Extract the passive assets that are needed for the fully RPC driven
2236
2236
// flow.
2237
2237
passivePackets , err := r .cfg .AssetWallet .CreatePassiveAssets (
2238
- ctx , []* tappsbt.VPacket {fundedVPkt .VPacket },
2239
- fundedVPkt .InputCommitments ,
2238
+ ctx , fundedVPkt .VPackets , fundedVPkt .InputCommitments ,
2240
2239
)
2241
2240
if err != nil {
2242
2241
return nil , fmt .Errorf ("error creating passive assets: %w" , err )
@@ -2257,7 +2256,12 @@ func (r *rpcServer) FundVirtualPsbt(ctx context.Context,
2257
2256
}
2258
2257
}
2259
2258
2260
- response .FundedPsbt , err = serialize (fundedVPkt .VPacket )
2259
+ // TODO(guggero): Remove this once we support multiple packets.
2260
+ if len (fundedVPkt .VPackets ) > 1 {
2261
+ return nil , fmt .Errorf ("only one packet supported" )
2262
+ }
2263
+
2264
+ response .FundedPsbt , err = serialize (fundedVPkt .VPackets [0 ])
2261
2265
if err != nil {
2262
2266
return nil , fmt .Errorf ("error serializing packet: %w" , err )
2263
2267
}
@@ -3387,11 +3391,13 @@ func (r *rpcServer) BurnAsset(ctx context.Context,
3387
3391
// We found the asset group, so we can use the group key to
3388
3392
// burn the asset.
3389
3393
groupKey = & assetGroup .GroupPubKey
3394
+
3390
3395
case errors .Is (err , address .ErrAssetGroupUnknown ):
3391
3396
// We don't know the asset group, so we'll try to burn the
3392
3397
// asset using the asset ID only.
3393
3398
rpcsLog .Debug ("Asset group key not found, asset may not be " +
3394
3399
"part of a group" )
3400
+
3395
3401
case err != nil :
3396
3402
return nil , fmt .Errorf ("error querying asset group: %w" , err )
3397
3403
}
@@ -3419,16 +3425,22 @@ func (r *rpcServer) BurnAsset(ctx context.Context,
3419
3425
return nil , fmt .Errorf ("error funding burn: %w" , err )
3420
3426
}
3421
3427
3428
+ // We don't support burning by group key yet, so we only expect a single
3429
+ // vPacket (which implies a single asset ID is involved).
3430
+ if len (fundResp .VPackets ) > 1 {
3431
+ return nil , fmt .Errorf ("only one packet supported" )
3432
+ }
3433
+
3422
3434
// Now we can sign the packet and send it to the chain.
3423
- _ , err = r .cfg .AssetWallet .SignVirtualPacket (fundResp .VPacket )
3435
+ vPkt := fundResp .VPackets [0 ]
3436
+ _ , err = r .cfg .AssetWallet .SignVirtualPacket (vPkt )
3424
3437
if err != nil {
3425
3438
return nil , fmt .Errorf ("error signing packet: %w" , err )
3426
3439
}
3427
3440
3428
3441
resp , err := r .cfg .ChainPorter .RequestShipment (
3429
3442
tapfreighter .NewPreSignedParcel (
3430
- []* tappsbt.VPacket {fundResp .VPacket },
3431
- fundResp .InputCommitments , in .Note ,
3443
+ fundResp .VPackets , fundResp .InputCommitments , in .Note ,
3432
3444
),
3433
3445
)
3434
3446
if err != nil {
@@ -3443,7 +3455,7 @@ func (r *rpcServer) BurnAsset(ctx context.Context,
3443
3455
3444
3456
var burnProof * taprpc.DecodedProof
3445
3457
for idx := range resp .Outputs {
3446
- vOut := fundResp . VPacket .Outputs [idx ]
3458
+ vOut := vPkt .Outputs [idx ]
3447
3459
tOut := resp .Outputs [idx ]
3448
3460
if vOut .Asset .IsBurn () {
3449
3461
p , err := proof .Decode (tOut .ProofSuffix )
@@ -6427,54 +6439,71 @@ func MarshalAssetFedSyncCfg(
6427
6439
}
6428
6440
6429
6441
// unmarshalAssetSpecifier unmarshals an asset specifier from the RPC form.
6430
- func unmarshalAssetSpecifier (req * rfqrpc.AssetSpecifier ) (* asset.ID ,
6442
+ func unmarshalAssetSpecifier (s * rfqrpc.AssetSpecifier ) (* asset.ID ,
6431
6443
* btcec.PublicKey , error ) {
6432
6444
6445
+ if s == nil {
6446
+ return nil , nil , fmt .Errorf ("asset specifier must be specified" )
6447
+ }
6448
+
6449
+ return parseAssetSpecifier (
6450
+ s .GetAssetId (), s .GetAssetIdStr (), s .GetGroupKey (),
6451
+ s .GetGroupKeyStr (),
6452
+ )
6453
+ }
6454
+
6455
+ // parseAssetSpecifier parses an asset specifier from the RPC form.
6456
+ func parseAssetSpecifier (reqAssetID []byte , reqAssetIDStr string ,
6457
+ reqGroupKey []byte , reqGroupKeyStr string ) (* asset.ID , * btcec.PublicKey ,
6458
+ error ) {
6459
+
6433
6460
// Attempt to decode the asset specifier from the RPC request. In cases
6434
6461
// where both the asset ID and asset group key are provided, we will
6435
6462
// give precedence to the asset ID due to its higher level of
6436
6463
// specificity.
6437
6464
var (
6438
- assetID * asset.ID
6439
-
6440
- groupKeyBytes []byte
6441
- groupKey * btcec.PublicKey
6442
-
6443
- err error
6465
+ assetID * asset.ID
6466
+ groupKey * btcec.PublicKey
6467
+ err error
6444
6468
)
6445
6469
6446
6470
switch {
6447
6471
// Parse the asset ID if it's set.
6448
- case len (req .GetAssetId ()) > 0 :
6472
+ case len (reqAssetID ) > 0 :
6473
+ if len (reqAssetID ) != sha256 .Size {
6474
+ return nil , nil , fmt .Errorf ("asset ID must be 32 bytes" )
6475
+ }
6476
+
6449
6477
var assetIdBytes [32 ]byte
6450
- copy (assetIdBytes [:], req . GetAssetId () )
6478
+ copy (assetIdBytes [:], reqAssetID )
6451
6479
id := asset .ID (assetIdBytes )
6452
6480
assetID = & id
6453
6481
6454
- case len (req . GetAssetIdStr () ) > 0 :
6455
- assetIDBytes , err := hex .DecodeString (req . GetAssetIdStr () )
6482
+ case len (reqAssetIDStr ) > 0 :
6483
+ assetIDBytes , err := hex .DecodeString (reqAssetIDStr )
6456
6484
if err != nil {
6457
6485
return nil , nil , fmt .Errorf ("error decoding asset " +
6458
6486
"ID: %w" , err )
6459
6487
}
6460
6488
6489
+ if len (assetIDBytes ) != sha256 .Size {
6490
+ return nil , nil , fmt .Errorf ("asset ID must be 32 bytes" )
6491
+ }
6492
+
6461
6493
var id asset.ID
6462
6494
copy (id [:], assetIDBytes )
6463
6495
assetID = & id
6464
6496
6465
6497
// Parse the group key if it's set.
6466
- case len (req .GetGroupKey ()) > 0 :
6467
- groupKeyBytes = req .GetGroupKey ()
6468
- groupKey , err = btcec .ParsePubKey (groupKeyBytes )
6498
+ case len (reqGroupKey ) > 0 :
6499
+ groupKey , err = btcec .ParsePubKey (reqGroupKey )
6469
6500
if err != nil {
6470
6501
return nil , nil , fmt .Errorf ("error parsing group " +
6471
6502
"key: %w" , err )
6472
6503
}
6473
6504
6474
- case len (req .GetGroupKeyStr ()) > 0 :
6475
- groupKeyBytes , err := hex .DecodeString (
6476
- req .GetGroupKeyStr (),
6477
- )
6505
+ case len (reqGroupKeyStr ) > 0 :
6506
+ groupKeyBytes , err := hex .DecodeString (reqGroupKeyStr )
6478
6507
if err != nil {
6479
6508
return nil , nil , fmt .Errorf ("error decoding group " +
6480
6509
"key: %w" , err )
@@ -7055,8 +7084,21 @@ func (r *rpcServer) FundChannel(ctx context.Context,
7055
7084
return nil , fmt .Errorf ("error parsing peer pubkey: %w" , err )
7056
7085
}
7057
7086
7058
- if len (req .AssetId ) != sha256 .Size {
7059
- return nil , fmt .Errorf ("asset ID must be 32 bytes" )
7087
+ assetID , groupKey , err := parseAssetSpecifier (
7088
+ req .GetAssetId (), "" , nil , "" ,
7089
+ )
7090
+ if err != nil {
7091
+ return nil , fmt .Errorf ("error parsing asset specifier: %w" , err )
7092
+ }
7093
+
7094
+ // For channel funding, we need to make sure that the group key is set
7095
+ // if the asset is grouped.
7096
+ assetSpecifier , err := r .specifierWithGroupKeyLookup (
7097
+ ctx , assetID , groupKey ,
7098
+ )
7099
+ if err != nil {
7100
+ return nil , fmt .Errorf ("error creating asset specifier: %w" ,
7101
+ err )
7060
7102
}
7061
7103
7062
7104
if req .AssetAmount == 0 {
@@ -7067,12 +7109,12 @@ func (r *rpcServer) FundChannel(ctx context.Context,
7067
7109
}
7068
7110
7069
7111
fundReq := tapchannel.FundReq {
7070
- PeerPub : * peerPub ,
7071
- AssetAmount : req .AssetAmount ,
7072
- FeeRate : chainfee .SatPerVByte (req .FeeRateSatPerVbyte ),
7073
- PushAmount : btcutil .Amount (req .PushSat ),
7112
+ PeerPub : * peerPub ,
7113
+ AssetSpecifier : assetSpecifier ,
7114
+ AssetAmount : req .AssetAmount ,
7115
+ FeeRate : chainfee .SatPerVByte (req .FeeRateSatPerVbyte ),
7116
+ PushAmount : btcutil .Amount (req .PushSat ),
7074
7117
}
7075
- copy (fundReq .AssetID [:], req .AssetId )
7076
7118
7077
7119
chanPoint , err := r .cfg .AuxFundingController .FundChannel (ctx , fundReq )
7078
7120
if err != nil {
@@ -7085,6 +7127,30 @@ func (r *rpcServer) FundChannel(ctx context.Context,
7085
7127
}, nil
7086
7128
}
7087
7129
7130
+ // specifierWithGroupKeyLookup returns an asset specifier that has the group key
7131
+ // set if it's a grouped asset.
7132
+ func (r * rpcServer ) specifierWithGroupKeyLookup (ctx context.Context ,
7133
+ assetID * asset.ID , groupKey * btcec.PublicKey ) (asset.Specifier , error ) {
7134
+
7135
+ var result asset.Specifier
7136
+
7137
+ if assetID != nil && groupKey == nil {
7138
+ dbGroupKey , err := r .cfg .TapAddrBook .QueryAssetGroup (
7139
+ ctx , * assetID ,
7140
+ )
7141
+ switch {
7142
+ case err == nil && dbGroupKey .GroupKey != nil :
7143
+ groupKey = & dbGroupKey .GroupPubKey
7144
+
7145
+ case err != nil :
7146
+ return result , fmt .Errorf ("unable to query asset " +
7147
+ "group: %w" , err )
7148
+ }
7149
+ }
7150
+
7151
+ return asset .NewSpecifier (assetID , groupKey , nil , true )
7152
+ }
7153
+
7088
7154
// EncodeCustomRecords allows RPC users to encode Taproot Asset channel related
7089
7155
// data into the TLV format that is used in the custom records of the lnd
7090
7156
// payment or other channel related RPCs. This RPC is completely stateless and
0 commit comments