@@ -16,6 +16,7 @@ import (
1616 wrpc "github.com/lightninglabs/taproot-assets/taprpc/assetwalletrpc"
1717 "github.com/lightninglabs/taproot-assets/taprpc/mintrpc"
1818 "github.com/lightninglabs/taproot-assets/taprpc/tapdevrpc"
19+ unirpc "github.com/lightninglabs/taproot-assets/taprpc/universerpc"
1920 "github.com/lightningnetwork/lnd/lntest/wait"
2021 "github.com/stretchr/testify/require"
2122)
@@ -664,6 +665,194 @@ func testReattemptFailedSendHashmailCourier(t *harnessTest) {
664665 wg .Wait ()
665666}
666667
668+ // testReattemptProofTransferOnTapdRestart tests that a failed attempt at
669+ // transferring a transfer output proof to a proof courier will be reattempted
670+ // by the sending tapd node upon restart. This test targets the universe
671+ // courier.
672+ func testReattemptProofTransferOnTapdRestart (t * harnessTest ) {
673+ var (
674+ ctxb = context .Background ()
675+ wg sync.WaitGroup
676+ )
677+
678+ // For this test we will use the universe server as the proof courier.
679+ proofCourier := t .universeServer
680+
681+ // Make a new tapd node which will send an asset to a receiving tapd
682+ // node.
683+ sendTapd := setupTapdHarness (
684+ t .t , t , t .lndHarness .Bob , t .universeServer ,
685+ func (params * tapdHarnessParams ) {
686+ params .expectErrExit = true
687+ params .proofCourier = proofCourier
688+ },
689+ )
690+ defer func () {
691+ // Any node that has been started within an itest should be
692+ // explicitly stopped within the same itest.
693+ require .NoError (t .t , sendTapd .stop (! * noDelete ))
694+ }()
695+
696+ // Use the primary tapd node as the receiver node.
697+ recvTapd := t .tapd
698+
699+ // Use the sending node to mint an asset for sending.
700+ rpcAssets := MintAssetsConfirmBatch (
701+ t .t , t .lndHarness .Miner .Client , sendTapd ,
702+ []* mintrpc.MintAssetRequest {simpleAssets [0 ]},
703+ )
704+
705+ genInfo := rpcAssets [0 ].AssetGenesis
706+
707+ // After minting an asset with the sending node, we need to synchronize
708+ // the Universe state to ensure the receiving node is updated and aware
709+ // of the asset.
710+ t .syncUniverseState (sendTapd , recvTapd , len (rpcAssets ))
711+
712+ // Create a new address for the receiver node. We will use the universe
713+ // server as the proof courier.
714+ proofCourierAddr := fmt .Sprintf (
715+ "%s://%s" , proof .UniverseRpcCourierType ,
716+ proofCourier .service .rpcHost (),
717+ )
718+ t .Logf ("Proof courier address: %s" , proofCourierAddr )
719+
720+ recvAddr , err := recvTapd .NewAddr (ctxb , & taprpc.NewAddrRequest {
721+ AssetId : genInfo .AssetId ,
722+ Amt : 10 ,
723+ ProofCourierAddr : proofCourierAddr ,
724+ })
725+ require .NoError (t .t , err )
726+ AssertAddrCreated (t .t , recvTapd , rpcAssets [0 ], recvAddr )
727+
728+ // Soon we will be attempting to send an asset to the receiver node. We
729+ // want the attempt to fail until we restart the sending node.
730+ // Therefore, we will take the proof courier service offline.
731+ t .Log ("Stopping proof courier service" )
732+ require .NoError (t .t , proofCourier .Stop ())
733+
734+ // Now that the proof courier service is offline, the sending node's
735+ // attempt to transfer the asset proof should fail.
736+ //
737+ // We will soon start the asset transfer process. However, before we
738+ // start, we subscribe to the send events from the sending tapd node so
739+ // that we can be sure that a transfer has been attempted.
740+ events := SubscribeSendEvents (t .t , sendTapd )
741+
742+ wg .Add (1 )
743+ go func () {
744+ defer wg .Done ()
745+
746+ // Define a target event selector to match the backoff wait
747+ // event. This function selects for a specific event type.
748+ targetEventSelector := func (
749+ event * tapdevrpc.SendAssetEvent ) bool {
750+
751+ return AssertSendEventProofTransferBackoffWaitTypeSend (
752+ t , event ,
753+ )
754+ }
755+
756+ // Expected number of events is one less than the number of
757+ // tries because the first attempt does not count as a backoff
758+ // event.
759+ nodeBackoffCfg :=
760+ sendTapd .clientCfg .UniverseRpcCourier .BackoffCfg
761+ expectedEventCount := nodeBackoffCfg .NumTries - 1
762+
763+ // Context timeout scales with expected number of events.
764+ timeout := time .Duration (expectedEventCount ) *
765+ defaultProofTransferReceiverAckTimeout
766+
767+ // Allow for some margin for the operations that aren't pure
768+ // waiting on the receiver ACK.
769+ timeout += timeoutMargin
770+
771+ assertAssetNtfsEvent (
772+ t , events , timeout , targetEventSelector ,
773+ expectedEventCount ,
774+ )
775+ }()
776+
777+ // Start asset transfer and then mine to confirm the associated on-chain
778+ // tx. The on-chain tx should be mined successfully, but we expect the
779+ // asset proof transfer to be unsuccessful.
780+ sendResp , _ := sendAssetsToAddr (t , sendTapd , recvAddr )
781+ MineBlocks (t .t , t .lndHarness .Miner .Client , 1 , 1 )
782+
783+ // Wait to ensure that the asset transfer attempt has been made.
784+ wg .Wait ()
785+
786+ // Stop the sending tapd node. This downtime will give us the
787+ // opportunity to restart the proof courier service.
788+ t .Log ("Stopping sending tapd node" )
789+ require .NoError (t .t , sendTapd .stop (false ))
790+
791+ // Restart the proof courier service.
792+ t .Log ("Starting proof courier service" )
793+ require .NoError (t .t , proofCourier .Start (nil ))
794+ t .Logf ("Proof courier address: %s" , proofCourier .service .rpcHost ())
795+
796+ // Ensure that the proof courier address has not changed on restart.
797+ // The port is currently selected opportunistically.
798+ // If the proof courier address has changed the tap address will be
799+ // stale.
800+ newProofCourierAddr := fmt .Sprintf (
801+ "%s://%s" , proof .UniverseRpcCourierType ,
802+ proofCourier .service .rpcHost (),
803+ )
804+ require .Equal (t .t , proofCourierAddr , newProofCourierAddr )
805+
806+ // Identify receiver's asset transfer output.
807+ require .Len (t .t , sendResp .Transfer .Outputs , 2 )
808+ recvOutput := sendResp .Transfer .Outputs [0 ]
809+
810+ // If the script key of the output is local to the sending node, then
811+ // the receiver's output is the second output.
812+ if recvOutput .ScriptKeyIsLocal {
813+ recvOutput = sendResp .Transfer .Outputs [1 ]
814+ }
815+
816+ // Formulate a universe key to query the proof courier for the asset
817+ // transfer proof.
818+ uniKey := unirpc.UniverseKey {
819+ Id : & unirpc.ID {
820+ Id : & unirpc.ID_AssetId {
821+ AssetId : genInfo .AssetId ,
822+ },
823+ ProofType : unirpc .ProofType_PROOF_TYPE_TRANSFER ,
824+ },
825+ LeafKey : & unirpc.AssetKey {
826+ Outpoint : & unirpc.AssetKey_OpStr {
827+ OpStr : recvOutput .Anchor .Outpoint ,
828+ },
829+ ScriptKey : & unirpc.AssetKey_ScriptKeyBytes {
830+ ScriptKeyBytes : recvOutput .ScriptKey ,
831+ },
832+ },
833+ }
834+
835+ // Ensure that the transfer proof has not reached the proof courier yet.
836+ resp , err := proofCourier .service .QueryProof (ctxb , & uniKey )
837+ require .Nil (t .t , resp )
838+ require .ErrorContains (t .t , err , "no universe proof found" )
839+
840+ // Restart the sending tapd node. The node should reattempt to transfer
841+ // the asset proof to the proof courier.
842+ t .Log ("Restarting sending tapd node" )
843+ require .NoError (t .t , sendTapd .start (false ))
844+
845+ require .Eventually (t .t , func () bool {
846+ resp , err = proofCourier .service .QueryProof (ctxb , & uniKey )
847+ return err == nil && resp != nil
848+ }, defaultWaitTimeout , 200 * time .Millisecond )
849+
850+ // TODO(ffranr): Modify the receiver node proof retrieval backoff
851+ // schedule such that we can assert that the transfer fully completes
852+ // in a timely and predictable manner.
853+ // AssertNonInteractiveRecvComplete(t.t, recvTapd, 1)
854+ }
855+
667856// testReattemptFailedSendUniCourier tests that a failed attempt at
668857// sending an asset proof will be reattempted by the tapd node. This test
669858// targets the universe proof courier.
0 commit comments