@@ -2235,8 +2235,7 @@ func (r *rpcServer) FundVirtualPsbt(ctx context.Context,
22352235 // Extract the passive assets that are needed for the fully RPC driven
22362236 // flow.
22372237 passivePackets , err := r .cfg .AssetWallet .CreatePassiveAssets (
2238- ctx , []* tappsbt.VPacket {fundedVPkt .VPacket },
2239- fundedVPkt .InputCommitments ,
2238+ ctx , fundedVPkt .VPackets , fundedVPkt .InputCommitments ,
22402239 )
22412240 if err != nil {
22422241 return nil , fmt .Errorf ("error creating passive assets: %w" , err )
@@ -2257,7 +2256,12 @@ func (r *rpcServer) FundVirtualPsbt(ctx context.Context,
22572256 }
22582257 }
22592258
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 ])
22612265 if err != nil {
22622266 return nil , fmt .Errorf ("error serializing packet: %w" , err )
22632267 }
@@ -3387,11 +3391,13 @@ func (r *rpcServer) BurnAsset(ctx context.Context,
33873391 // We found the asset group, so we can use the group key to
33883392 // burn the asset.
33893393 groupKey = & assetGroup .GroupPubKey
3394+
33903395 case errors .Is (err , address .ErrAssetGroupUnknown ):
33913396 // We don't know the asset group, so we'll try to burn the
33923397 // asset using the asset ID only.
33933398 rpcsLog .Debug ("Asset group key not found, asset may not be " +
33943399 "part of a group" )
3400+
33953401 case err != nil :
33963402 return nil , fmt .Errorf ("error querying asset group: %w" , err )
33973403 }
@@ -3419,16 +3425,22 @@ func (r *rpcServer) BurnAsset(ctx context.Context,
34193425 return nil , fmt .Errorf ("error funding burn: %w" , err )
34203426 }
34213427
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+
34223434 // 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 )
34243437 if err != nil {
34253438 return nil , fmt .Errorf ("error signing packet: %w" , err )
34263439 }
34273440
34283441 resp , err := r .cfg .ChainPorter .RequestShipment (
34293442 tapfreighter .NewPreSignedParcel (
3430- []* tappsbt.VPacket {fundResp .VPacket },
3431- fundResp .InputCommitments , in .Note ,
3443+ fundResp .VPackets , fundResp .InputCommitments , in .Note ,
34323444 ),
34333445 )
34343446 if err != nil {
@@ -3443,7 +3455,7 @@ func (r *rpcServer) BurnAsset(ctx context.Context,
34433455
34443456 var burnProof * taprpc.DecodedProof
34453457 for idx := range resp .Outputs {
3446- vOut := fundResp . VPacket .Outputs [idx ]
3458+ vOut := vPkt .Outputs [idx ]
34473459 tOut := resp .Outputs [idx ]
34483460 if vOut .Asset .IsBurn () {
34493461 p , err := proof .Decode (tOut .ProofSuffix )
@@ -6427,54 +6439,71 @@ func MarshalAssetFedSyncCfg(
64276439}
64286440
64296441// 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 ,
64316443 * btcec.PublicKey , error ) {
64326444
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+
64336460 // Attempt to decode the asset specifier from the RPC request. In cases
64346461 // where both the asset ID and asset group key are provided, we will
64356462 // give precedence to the asset ID due to its higher level of
64366463 // specificity.
64376464 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
64446468 )
64456469
64466470 switch {
64476471 // 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+
64496477 var assetIdBytes [32 ]byte
6450- copy (assetIdBytes [:], req . GetAssetId () )
6478+ copy (assetIdBytes [:], reqAssetID )
64516479 id := asset .ID (assetIdBytes )
64526480 assetID = & id
64536481
6454- case len (req . GetAssetIdStr () ) > 0 :
6455- assetIDBytes , err := hex .DecodeString (req . GetAssetIdStr () )
6482+ case len (reqAssetIDStr ) > 0 :
6483+ assetIDBytes , err := hex .DecodeString (reqAssetIDStr )
64566484 if err != nil {
64576485 return nil , nil , fmt .Errorf ("error decoding asset " +
64586486 "ID: %w" , err )
64596487 }
64606488
6489+ if len (assetIDBytes ) != sha256 .Size {
6490+ return nil , nil , fmt .Errorf ("asset ID must be 32 bytes" )
6491+ }
6492+
64616493 var id asset.ID
64626494 copy (id [:], assetIDBytes )
64636495 assetID = & id
64646496
64656497 // 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 )
64696500 if err != nil {
64706501 return nil , nil , fmt .Errorf ("error parsing group " +
64716502 "key: %w" , err )
64726503 }
64736504
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 )
64786507 if err != nil {
64796508 return nil , nil , fmt .Errorf ("error decoding group " +
64806509 "key: %w" , err )
@@ -7055,8 +7084,21 @@ func (r *rpcServer) FundChannel(ctx context.Context,
70557084 return nil , fmt .Errorf ("error parsing peer pubkey: %w" , err )
70567085 }
70577086
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 )
70607102 }
70617103
70627104 if req .AssetAmount == 0 {
@@ -7067,12 +7109,12 @@ func (r *rpcServer) FundChannel(ctx context.Context,
70677109 }
70687110
70697111 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 ),
70747117 }
7075- copy (fundReq .AssetID [:], req .AssetId )
70767118
70777119 chanPoint , err := r .cfg .AuxFundingController .FundChannel (ctx , fundReq )
70787120 if err != nil {
@@ -7085,6 +7127,30 @@ func (r *rpcServer) FundChannel(ctx context.Context,
70857127 }, nil
70867128}
70877129
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+
70887154// EncodeCustomRecords allows RPC users to encode Taproot Asset channel related
70897155// data into the TLV format that is used in the custom records of the lnd
70907156// payment or other channel related RPCs. This RPC is completely stateless and
0 commit comments