@@ -3383,3 +3383,195 @@ func TestUpsertAssetsWithSplitCommitments(t *testing.T) {
33833383 })
33843384 }
33853385}
3386+
3387+ // TestFetchOrphanUTXOs tests that FetchOrphanUTXOs:
3388+ // 1. Filters out UTXOs with missing signing info (KeyFamily=0 AND KeyIndex=0)
3389+ // 2. Respects the MaxOrphanUTXOs limit.
3390+ func TestFetchOrphanUTXOs (t * testing.T ) {
3391+ t .Parallel ()
3392+
3393+ testCases := []struct {
3394+ name string
3395+ utxosToInsert [][2 ]int32
3396+ expectedCount int
3397+ checkFunc func (t * testing.T ,
3398+ orphans []* tapfreighter.ZeroValueInput )
3399+ }{
3400+ {
3401+ name : "filters missing signing info" ,
3402+ utxosToInsert : [][2 ]int32 {
3403+ {212 , 5 }, // Valid signing info.
3404+ {0 , 0 }, // Missing signing info.
3405+ },
3406+ expectedCount : 1 ,
3407+ checkFunc : func (t * testing.T ,
3408+ orphans []* tapfreighter.ZeroValueInput ) {
3409+
3410+ require .Equal (
3411+ t , keychain .KeyFamily (212 ),
3412+ orphans [0 ].InternalKey .Family ,
3413+ )
3414+ require .Equal (
3415+ t , uint32 (5 ),
3416+ orphans [0 ].InternalKey .Index ,
3417+ )
3418+ },
3419+ },
3420+ {
3421+ name : "respects max limit" ,
3422+ // Insert MaxOrphanUTXOs + 10 UTXOs with valid signing
3423+ // info.
3424+ utxosToInsert : func () [][2 ]int32 {
3425+ utxos := make ([][2 ]int32 , MaxOrphanUTXOs + 10 )
3426+ for i := range utxos {
3427+ utxos [i ] = [2 ]int32 {
3428+ int32 (i + 1 ), int32 (i + 1 ),
3429+ }
3430+ }
3431+ return utxos
3432+ }(),
3433+ expectedCount : MaxOrphanUTXOs ,
3434+ checkFunc : func (t * testing.T ,
3435+ orphans []* tapfreighter.ZeroValueInput ) {
3436+
3437+ // Verify all returned UTXOs have valid signing
3438+ // info.
3439+ for _ , orphan := range orphans {
3440+ require .NotZero (
3441+ t , orphan .InternalKey .Family ,
3442+ )
3443+ require .NotZero (
3444+ t , orphan .InternalKey .Index ,
3445+ )
3446+ }
3447+ },
3448+ },
3449+ }
3450+
3451+ for _ , tc := range testCases {
3452+ t .Run (tc .name , func (t * testing.T ) {
3453+ _ , assetsStore , db := newAssetStore (t )
3454+ ctx := context .Background ()
3455+
3456+ // Insert all UTXOs for this test case.
3457+ for _ , keyInfo := range tc .utxosToInsert {
3458+ insertOrphanUTXO (
3459+ t , ctx , db , assetsStore ,
3460+ keyInfo [0 ], keyInfo [1 ],
3461+ )
3462+ }
3463+
3464+ // Fetch orphan UTXOs.
3465+ orphans , err := assetsStore .FetchOrphanUTXOs (ctx )
3466+ require .NoError (t , err )
3467+
3468+ require .Len (t , orphans , tc .expectedCount )
3469+
3470+ if tc .checkFunc != nil {
3471+ tc .checkFunc (t , orphans )
3472+ }
3473+ })
3474+ }
3475+ }
3476+
3477+ // insertOrphanUTXO inserts a managed UTXO with a tombstone asset and the
3478+ // specified KeyFamily and KeyIndex values for the internal key.
3479+ func insertOrphanUTXO (t * testing.T , ctx context.Context , db sqlc.Querier ,
3480+ assetsStore * AssetStore , keyFamily , keyIndex int32 ) wire.OutPoint {
3481+
3482+ internalKey := test .RandPubKey (t )
3483+
3484+ // Insert the internal key with specific KeyFamily and KeyIndex.
3485+ _ , err := db .UpsertInternalKey (ctx , sqlc.UpsertInternalKeyParams {
3486+ RawKey : internalKey .SerializeCompressed (),
3487+ KeyFamily : keyFamily ,
3488+ KeyIndex : keyIndex ,
3489+ })
3490+ require .NoError (t , err )
3491+
3492+ // Create a chain transaction.
3493+ anchorTx := wire .NewMsgTx (2 )
3494+ anchorTx .AddTxIn (& wire.TxIn {
3495+ PreviousOutPoint : test .RandOp (t ),
3496+ })
3497+ anchorTx .AddTxOut (& wire.TxOut {
3498+ PkScript : bytes .Repeat ([]byte {0x01 }, 34 ),
3499+ Value : 1000 ,
3500+ })
3501+
3502+ txBytes , err := fn .Serialize (anchorTx )
3503+ require .NoError (t , err )
3504+
3505+ txHash := anchorTx .TxHash ()
3506+ chainTxID , err := db .UpsertChainTx (ctx , sqlc.UpsertChainTxParams {
3507+ Txid : txHash [:],
3508+ RawTx : txBytes ,
3509+ BlockHeight : sqlInt32 (100 ),
3510+ })
3511+ require .NoError (t , err )
3512+
3513+ outpoint := wire.OutPoint {
3514+ Hash : txHash ,
3515+ Index : 0 ,
3516+ }
3517+ outpointBytes , err := encodeOutpoint (outpoint )
3518+ require .NoError (t , err )
3519+
3520+ // Insert the managed UTXO.
3521+ _ , err = db .UpsertManagedUTXO (ctx , sqlc.UpsertManagedUTXOParams {
3522+ RawKey : internalKey .SerializeCompressed (),
3523+ Outpoint : outpointBytes ,
3524+ AmtSats : 1000 ,
3525+ TaprootAssetRoot : test .RandBytes (32 ),
3526+ MerkleRoot : test .RandBytes (32 ),
3527+ TxnID : chainTxID ,
3528+ })
3529+ require .NoError (t , err )
3530+
3531+ // Create and insert a tombstone asset for this UTXO.
3532+ gen := asset .RandGenesis (t , asset .Normal )
3533+ gen .FirstPrevOut = outpoint
3534+ tombstone := asset .NewAssetNoErr (
3535+ t , gen , 0 , 0 , 0 , asset .NUMSScriptKey , nil ,
3536+ )
3537+
3538+ assetCommitment , err := commitment .NewAssetCommitment (tombstone )
3539+ require .NoError (t , err )
3540+
3541+ tapCommitment , err := commitment .NewTapCommitment (nil , assetCommitment )
3542+ require .NoError (t , err )
3543+
3544+ txMerkleProof , err := proof .NewTxMerkleProof ([]* wire.MsgTx {anchorTx }, 0 )
3545+ require .NoError (t , err )
3546+
3547+ assetProof := proof.Proof {
3548+ AnchorTx : * anchorTx ,
3549+ BlockHeight : 100 ,
3550+ TxMerkleProof : * txMerkleProof ,
3551+ Asset : * tombstone ,
3552+ InclusionProof : proof.TaprootProof {
3553+ OutputIndex : 0 ,
3554+ InternalKey : internalKey ,
3555+ },
3556+ }
3557+
3558+ proofBlob , err := proof .EncodeAsProofFile (& assetProof )
3559+ require .NoError (t , err )
3560+
3561+ err = assetsStore .ImportProofs (
3562+ ctx , proof .MockVerifierCtx , false ,
3563+ & proof.AnnotatedProof {
3564+ AssetSnapshot : & proof.AssetSnapshot {
3565+ AnchorTx : anchorTx ,
3566+ InternalKey : internalKey ,
3567+ Asset : tombstone ,
3568+ ScriptRoot : tapCommitment ,
3569+ AnchorBlockHeight : 100 ,
3570+ },
3571+ Blob : proofBlob ,
3572+ },
3573+ )
3574+ require .NoError (t , err )
3575+
3576+ return outpoint
3577+ }
0 commit comments