@@ -3,6 +3,8 @@ package itest
33import (
44 "bytes"
55 "context"
6+ "fmt"
7+ "log"
68 "testing"
79 "time"
810
@@ -30,6 +32,7 @@ import (
3032 "github.com/lightningnetwork/lnd/lnrpc"
3133 "github.com/lightningnetwork/lnd/lntest/node"
3234 "github.com/lightningnetwork/lnd/lnwallet/chainfee"
35+ "github.com/lightningnetwork/lnd/tlv"
3336 "github.com/stretchr/testify/require"
3437 "google.golang.org/grpc"
3538 "google.golang.org/protobuf/proto"
@@ -735,6 +738,151 @@ func ManualMintSimpleAsset(t *harnessTest, lndNode *node.HarnessNode,
735738 return mintedAsset [0 ], & importReq
736739}
737740
741+ // ExternalSigRes is a helper struct that holds the signed PSBT and the
742+ // corresponding asset ID.
743+ type ExternalSigRes struct {
744+ SignedPsbt psbt.Packet
745+ AssetID asset.ID
746+ }
747+
748+ // ExternalSigCallback is a callback function that is called to sign the group
749+ // virtual PSBT with external signers.
750+ type ExternalSigCallback func ([]* mintrpc.UnsealedAsset ) []ExternalSigRes
751+
752+ // MintAssetExternalSigner is a helper function that mints a batch of assets and
753+ // calls the external signer callback to sign the group virtual PSBT.
754+ func MintAssetExternalSigner (t * harnessTest , tapNode * tapdHarness ,
755+ assetReqs []* mintrpc.MintAssetRequest ,
756+ externalSignerCallback ExternalSigCallback ) []* taprpc.Asset {
757+
758+ BuildMintingBatch (t .t , tapNode , assetReqs )
759+
760+ // Fund mint batch with BTC.
761+ ctxb := context .Background ()
762+ ctxt , cancel := context .WithTimeout (ctxb , defaultWaitTimeout )
763+
764+ fundResp , err := tapNode .FundBatch (ctxt , & mintrpc.FundBatchRequest {})
765+ require .NoError (t .t , err )
766+
767+ // Cancel the context for the fund request call.
768+ cancel ()
769+
770+ require .NotEmpty (t .t , fundResp .Batch )
771+ require .Equal (
772+ t .t , mintrpc .BatchState_BATCH_STATE_PENDING ,
773+ fundResp .Batch .Batch .State ,
774+ )
775+ require .Len (t .t , fundResp .Batch .UnsealedAssets , 1 )
776+
777+ // Pass unsealed assets to external signer callback to sign the group
778+ // virtual PSBT.
779+ callbackRes := externalSignerCallback (fundResp .Batch .UnsealedAssets )
780+
781+ // Extract group witness from signed PSBTs.
782+ var groupWitnesses []* taprpc.GroupWitness
783+ for idx := range callbackRes {
784+ res := callbackRes [idx ]
785+ signedPsbt := res .SignedPsbt
786+ genesisAssetID := res .AssetID
787+
788+ // Sanity check signed PSBT.
789+ require .Len (t .t , signedPsbt .Inputs , 1 )
790+ require .Len (t .t , signedPsbt .Outputs , 1 )
791+
792+ // Extract witness from signed PSBT.
793+ witnessStack , err := DeserializeWitnessStack (
794+ signedPsbt .Inputs [0 ].FinalScriptWitness ,
795+ )
796+ if err != nil {
797+ log .Fatalf ("Failed to deserialize witness stack: %v" ,
798+ err )
799+ }
800+
801+ groupWitness := taprpc.GroupWitness {
802+ GenesisId : genesisAssetID [:],
803+ Witness : witnessStack ,
804+ }
805+
806+ groupWitnesses = append (groupWitnesses , & groupWitness )
807+ }
808+
809+ // Seal the batch with the group witnesses.
810+ ctxt , cancel = context .WithTimeout (ctxb , defaultWaitTimeout )
811+
812+ sealReq := mintrpc.SealBatchRequest {
813+ GroupWitnesses : groupWitnesses ,
814+ }
815+ sealResp , err := tapNode .SealBatch (ctxt , & sealReq )
816+ require .NoError (t .t , err )
817+
818+ // Cancel the context for the seal request call.
819+ cancel ()
820+
821+ require .NotEmpty (t .t , sealResp .Batch )
822+
823+ // With the batch sealed successfully, we can now finalize it and
824+ // broadcast the anchor TX.
825+ ctxt , cancel = context .WithCancel (context .Background ())
826+ defer cancel ()
827+ stream , err := tapNode .SubscribeMintEvents (
828+ ctxt , & mintrpc.SubscribeMintEventsRequest {},
829+ )
830+ require .NoError (t .t , err )
831+ sub := & EventSubscription [* mintrpc.MintEvent ]{
832+ ClientEventStream : stream ,
833+ Cancel : cancel ,
834+ }
835+
836+ batchTXID , batchKey := FinalizeBatchUnconfirmed (
837+ t .t , t .lndHarness .Miner ().Client , tapNode , assetReqs ,
838+ )
839+ batchAssets := ConfirmBatch (
840+ t .t , t .lndHarness .Miner ().Client , tapNode , assetReqs , sub ,
841+ batchTXID , batchKey ,
842+ )
843+
844+ return batchAssets
845+ }
846+
847+ // DeserializeWitnessStack deserializes a serialized witness stack into a
848+ // [][]byte.
849+ //
850+ // TODO(ffranr): Reconcile this function with asset.TxWitnessDecoder.
851+ func DeserializeWitnessStack (serialized []byte ) ([][]byte , error ) {
852+ var (
853+ // buf is a general scratch buffer used when reading.
854+ buf [8 ]byte
855+
856+ stack [][]byte
857+ )
858+ reader := bytes .NewReader (serialized )
859+
860+ // Read the number of witness elements (compact size integer)
861+ count , err := tlv .ReadVarInt (reader , & buf )
862+ if err != nil {
863+ return nil , fmt .Errorf ("failed to read witness count: %w" , err )
864+ }
865+
866+ // Read each witness element
867+ for i := uint64 (0 ); i < count ; i ++ {
868+ elementSize , err := tlv .ReadVarInt (reader , & buf )
869+ if err != nil {
870+ return nil , fmt .Errorf ("failed to read witness " +
871+ "element size: %w" , err )
872+ }
873+
874+ // Read the witness element data
875+ element := make ([]byte , elementSize )
876+ if _ , err := reader .Read (element ); err != nil {
877+ return nil , fmt .Errorf ("failed to read witness " +
878+ "element data: %w" , err )
879+ }
880+ stack = append (stack , element )
881+ }
882+
883+ return stack , nil
884+ }
885+
738886// SyncUniverses syncs the universes of two tapd instances and waits until they
739887// are in sync.
740888func SyncUniverses (ctx context.Context , t * testing.T , clientTapd ,
0 commit comments