@@ -14,6 +14,7 @@ import (
1414 "github.com/btcsuite/btcd/btcec/v2/schnorr"
1515 "github.com/btcsuite/btcd/btcutil"
1616 "github.com/btcsuite/btcd/chaincfg/chainhash"
17+ "github.com/btcsuite/btcd/wire"
1718 "github.com/lightninglabs/taproot-assets/asset"
1819 "github.com/lightninglabs/taproot-assets/itest"
1920 "github.com/lightninglabs/taproot-assets/proof"
@@ -2302,6 +2303,318 @@ func testCustomChannelsBreach(ctx context.Context, net *NetworkHarness,
23022303 t .Logf ("Charlie balance after breach: %d" , charlieBalance )
23032304}
23042305
2306+ // testCustomChannelsV1Upgrade tests the upgrade path of a taproot assets
2307+ // channel. It upgrades one of the peers to a version that utilizes feature bits
2308+ // and new features over the channel, testing that backwards compatibility is
2309+ // maintained along the way. We also introduce a channel breach, right at the
2310+ // point before we switched over to the new features, to test that sweeping is
2311+ // done properly.
2312+ func testCustomChannelsV1Upgrade (ctx context.Context , net * NetworkHarness ,
2313+ t * harnessTest ) {
2314+
2315+ lndArgs := slices .Clone (lndArgsTemplate )
2316+ litdArgs := slices .Clone (litdArgsTemplate )
2317+
2318+ zane , err := net .NewNode (
2319+ t .t , "Zane" , lndArgs , false , true , litdArgs ... ,
2320+ )
2321+ require .NoError (t .t , err )
2322+
2323+ litdArgs = append (litdArgs , fmt .Sprintf (
2324+ "--taproot-assets.proofcourieraddr=%s://%s" ,
2325+ proof .UniverseRpcCourierType , zane .Cfg .LitAddr (),
2326+ ))
2327+
2328+ davePort := port .NextAvailablePort ()
2329+ daveFlags := append (
2330+ slices .Clone (lndArgs ), "--nolisten" , "--minbackoff=1h" ,
2331+ )
2332+
2333+ // For this simple test, we'll just have Charlie -> Dave as an assets
2334+ // channel.
2335+ dave , err := net .NewNodeWithPort (
2336+ t .t , "Dave" , daveFlags , false , true , davePort ,
2337+ litdArgs ... ,
2338+ )
2339+ require .NoError (t .t , err )
2340+
2341+ charlie , err := net .NewNode (t .t , "Charlie" , lndArgs , false , true , litdArgs ... )
2342+ require .NoError (t .t , err )
2343+
2344+ // Next we'll connect all the nodes and also fund them with some coins.
2345+ nodes := []* HarnessNode {dave , charlie }
2346+ connectAllNodes (t .t , net , nodes )
2347+ fundAllNodes (t .t , net , nodes )
2348+
2349+ universeTap := newTapClient (t .t , zane )
2350+ charlieTap := newTapClient (t .t , charlie )
2351+ daveTap := newTapClient (t .t , dave )
2352+
2353+ // Now we'll make an asset for Charlie that we'll use in the test to
2354+ // open a channel.
2355+ mintedAssets := itest .MintAssetsConfirmBatch (
2356+ t .t , t .lndHarness .Miner .Client , charlieTap ,
2357+ []* mintrpc.MintAssetRequest {
2358+ {
2359+ Asset : itestAsset ,
2360+ },
2361+ },
2362+ )
2363+ cents := mintedAssets [0 ]
2364+ assetID := cents .AssetGenesis .AssetId
2365+
2366+ t .Logf ("Minted %d itest asset cents, syncing universes..." ,
2367+ cents .Amount )
2368+
2369+ syncUniverses (t .t , charlieTap , dave )
2370+ t .Logf ("Universes synced between all nodes, distributing assets..." )
2371+
2372+ // Next we can open an asset channel from Charlie -> Dave, then kick
2373+ // off the main scenario.
2374+ t .Logf ("Opening asset channels..." )
2375+ assetFundResp , err := charlieTap .FundChannel (
2376+ ctx , & tchrpc.FundChannelRequest {
2377+ AssetAmount : fundingAmount ,
2378+ AssetId : assetID ,
2379+ PeerPubkey : dave .PubKey [:],
2380+ FeeRateSatPerVbyte : 5 ,
2381+ },
2382+ )
2383+ require .NoError (t .t , err )
2384+ t .Logf ("Funded channel between Charlie and Dave: %v" , assetFundResp )
2385+
2386+ // With the channel open, mine 6 blocks to confirm it.
2387+ mineBlocks (t , net , 6 , 1 )
2388+
2389+ // A transfer for the funding transaction should be found in Charlie's
2390+ // DB.
2391+ fundingTxid , err := chainhash .NewHashFromStr (assetFundResp .Txid )
2392+ require .NoError (t .t , err )
2393+ assetFundingTransfer := locateAssetTransfers (
2394+ t .t , charlieTap , * fundingTxid ,
2395+ )
2396+
2397+ t .Logf ("Channel funding transfer: %v" ,
2398+ toProtoJSON (t .t , assetFundingTransfer ))
2399+
2400+ // Charlie's balance should reflect that the funding asset is now
2401+ // excluded from balance reporting by tapd.
2402+ itest .AssertBalances (
2403+ t .t , charlieTap , itestAsset .Amount - fundingAmount ,
2404+ itest .WithAssetID (assetID ), itest .WithNumUtxos (1 ),
2405+ )
2406+
2407+ // Make sure that Charlie properly uploaded funding proof to the
2408+ // Universe server.
2409+ fundingScriptTree := tapscript .NewChannelFundingScriptTree ()
2410+ fundingScriptKey := fundingScriptTree .TaprootKey
2411+ fundingScriptTreeBytes := fundingScriptKey .SerializeCompressed ()
2412+ assertUniverseProofExists (
2413+ t .t , universeTap , assetID , nil , fundingScriptTreeBytes ,
2414+ fmt .Sprintf (
2415+ "%v:%v" , assetFundResp .Txid , assetFundResp .OutputIndex ,
2416+ ),
2417+ )
2418+
2419+ // Make sure the channel shows the correct asset information.
2420+ assertAssetChan (
2421+ t .t , charlie , dave , fundingAmount , []* taprpc.Asset {cents },
2422+ )
2423+
2424+ // Before we start sending out payments, let's make sure each node can
2425+ // see the other one in the graph and has all required features.
2426+ require .NoError (t .t , t .lndHarness .AssertNodeKnown (charlie , dave ))
2427+ require .NoError (t .t , t .lndHarness .AssertNodeKnown (dave , charlie ))
2428+
2429+ logBalance (t .t , nodes , assetID , "start" )
2430+
2431+ // Let's dispatch 5 asset & 5 keysend payments from Charlie to Dave. At
2432+ // this point Charlie is running the old version of LiT.
2433+ for range 5 {
2434+ sendAssetKeySendPayment (
2435+ t .t , charlie , dave , 50 , assetID , fn .None [int64 ](),
2436+ )
2437+ sendKeySendPayment (t .t , charlie , dave , 1_000 )
2438+ }
2439+
2440+ logBalance (t .t , nodes , assetID , "before upgrade" )
2441+
2442+ // Let's assert that Charlie & Dave actually run different versions of
2443+ // taproot-assets. We expect Dave to be running the latest version,
2444+ // while Charlie is running an older version (v0.15.0).
2445+ daveInfo , err := daveTap .GetInfo (ctx , & taprpc.GetInfoRequest {})
2446+ require .NoError (t .t , err )
2447+
2448+ charlieInfo , err := charlieTap .GetInfo (ctx , & taprpc.GetInfoRequest {})
2449+ require .NoError (t .t , err )
2450+
2451+ require .NotEqual (t .t , daveInfo .Version , charlieInfo .Version )
2452+
2453+ res , err := charlie .ChannelBalance (ctx , & lnrpc.ChannelBalanceRequest {})
2454+ require .NoError (t .t , err )
2455+
2456+ charlieSatsBefore := res .LocalBalance
2457+
2458+ // Now we'll restart Charlie and assert that he upgraded. We also back
2459+ // up the DB at this point, in order to induce a breach later right at
2460+ // the switching point before upgrading the channel. We will verify that
2461+ // the breach transaction will be swept by the right party.
2462+ require .NoError (t .t , net .StopAndBackupDB (charlie , WithUpgrade ()))
2463+ connectAllNodes (t .t , net , nodes )
2464+
2465+ charlieInfo , err = charlieTap .GetInfo (ctx , & taprpc.GetInfoRequest {})
2466+ require .NoError (t .t , err )
2467+
2468+ // Dave and Charlie should both be running the same version (latest).
2469+ require .Equal (t .t , daveInfo .Version , charlieInfo .Version )
2470+
2471+ // Let's send another 5 asset and keysend payments from Charlie to Dave.
2472+ // Charlie is now on the latest version of LiT and the channel upgraded.
2473+ for range 5 {
2474+ sendAssetKeySendPayment (
2475+ t .t , charlie , dave , 50 , assetID , fn .None [int64 ](),
2476+ )
2477+ }
2478+
2479+ res , err = charlie .ChannelBalance (ctx , & lnrpc.ChannelBalanceRequest {})
2480+ require .NoError (t .t , err )
2481+
2482+ charlieSatsAfter := res .LocalBalance
2483+
2484+ // Because of no-op HTLCs, the satoshi balance of Charlie should not
2485+ // have shifted while sending the asset payments.
2486+ require .Equal (t .t , charlieSatsBefore , charlieSatsAfter )
2487+
2488+ logBalance (t .t , nodes , assetID , "after upgrade" )
2489+
2490+ // Now let's restart Charlie and restore the DB to the previous snapshot
2491+ // which corresponds to a previous (invalid) and unupgraded channel
2492+ // state.
2493+ require .NoError (t .t , net .StopAndRestoreDB (charlie ))
2494+
2495+ // With Charlie restored, we'll now execute the force close.
2496+ t .Logf ("Force close by Charlie to breach..." )
2497+ charlieChanPoint := & lnrpc.ChannelPoint {
2498+ OutputIndex : uint32 (assetFundResp .OutputIndex ),
2499+ FundingTxid : & lnrpc.ChannelPoint_FundingTxidStr {
2500+ FundingTxidStr : assetFundResp .Txid ,
2501+ },
2502+ }
2503+ _ , breachTxid , err := net .CloseChannel (charlie , charlieChanPoint , true )
2504+ require .NoError (t .t , err )
2505+
2506+ t .Logf ("Channel closed! Mining blocks, close_txid=%v" , breachTxid )
2507+
2508+ // Next, we'll mine a block to confirm the breach transaction.
2509+ mineBlocks (t , net , 1 , 1 )
2510+
2511+ // We should be able to find the transfer of the breach for both
2512+ // parties.
2513+ charlieBreachTransfer := locateAssetTransfers (
2514+ t .t , charlieTap , * breachTxid ,
2515+ )
2516+ daveBreachTransfer := locateAssetTransfers (
2517+ t .t , daveTap , * breachTxid ,
2518+ )
2519+
2520+ t .Logf ("Charlie breach transfer: %v" ,
2521+ toProtoJSON (t .t , charlieBreachTransfer ))
2522+ t .Logf ("Dave breach transfer: %v" ,
2523+ toProtoJSON (t .t , daveBreachTransfer ))
2524+
2525+ require .Len (t .t , charlieBreachTransfer .Outputs , 2 )
2526+ assetOutput := charlieBreachTransfer .Outputs [0 ]
2527+ assertUniverseProofExists (
2528+ t .t , universeTap , assetID , nil , assetOutput .ScriptKey ,
2529+ assetOutput .Anchor .Outpoint ,
2530+ )
2531+
2532+ op , err := wire .NewOutPointFromString (assetOutput .Anchor .Outpoint )
2533+ require .NoError (t .t , err )
2534+
2535+ // We'll manually export the proof of the breach transfer, in order to
2536+ // verify that it indeed did not use STXO proofs.
2537+ proofResp , err := daveTap .ExportProof (ctx , & taprpc.ExportProofRequest {
2538+ AssetId : assetID ,
2539+ ScriptKey : assetOutput .ScriptKey ,
2540+ Outpoint : & taprpc.OutPoint {
2541+ Txid : op .Hash [:],
2542+ OutputIndex : op .Index ,
2543+ },
2544+ })
2545+ require .NoError (t .t , err )
2546+
2547+ proofFile , err := proof .DecodeFile (proofResp .RawProofFile )
2548+ require .NoError (t .t , err )
2549+ require .Equal (t .t , proofFile .NumProofs (), 3 )
2550+ latestProof , err := proofFile .LastProof ()
2551+ require .NoError (t .t , err )
2552+
2553+ // This proof should not contain the STXO exclusion proofs, since the
2554+ // breach occured right before the channel upgraded.
2555+ stxoProofs := latestProof .ExclusionProofs [0 ].CommitmentProof .STXOProofs
2556+ require .Nil (t .t , stxoProofs )
2557+
2558+ // With the breach transaction mined, Dave should now have a transaction
2559+ // in the mempool sweeping *both* commitment outputs.
2560+ daveJusticeTxid , err := waitForNTxsInMempool (
2561+ net .Miner .Client , 1 , time .Second * 5 ,
2562+ )
2563+ require .NoError (t .t , err )
2564+
2565+ t .Logf ("Dave justice txid: %v" , daveJusticeTxid )
2566+
2567+ // Next, we'll mine a block to confirm Dave's justice transaction.
2568+ mineBlocks (t , net , 1 , 1 )
2569+
2570+ // Dave should now have a transfer for his justice transaction.
2571+ daveJusticeTransfer := locateAssetTransfers (
2572+ t .t , daveTap , * daveJusticeTxid [0 ],
2573+ )
2574+
2575+ t .Logf ("Dave justice transfer: %v" ,
2576+ toProtoJSON (t .t , daveJusticeTransfer ))
2577+
2578+ // Dave should claim all of the asset balance that was put into the
2579+ // channel.
2580+ daveBalance := uint64 (fundingAmount )
2581+
2582+ itest .AssertBalances (
2583+ t .t , daveTap , daveBalance , itest .WithAssetID (assetID ),
2584+ itest .WithNumUtxos (2 ),
2585+ )
2586+
2587+ t .Logf ("Dave balance after breach: %d" , daveBalance )
2588+
2589+ require .Len (t .t , daveJusticeTransfer .Outputs , 2 )
2590+ assetOutput = daveJusticeTransfer .Outputs [0 ]
2591+ op , err = wire .NewOutPointFromString (assetOutput .Anchor .Outpoint )
2592+ require .NoError (t .t , err )
2593+
2594+ // We'll now also export the proof for the justice transaction. Here we
2595+ // expect to find STXO proofs, as the sweeping party is an upgraded node
2596+ // that supports it.
2597+ proofResp , err = daveTap .ExportProof (ctx , & taprpc.ExportProofRequest {
2598+ AssetId : assetID ,
2599+ ScriptKey : assetOutput .ScriptKey ,
2600+ Outpoint : & taprpc.OutPoint {
2601+ Txid : op .Hash [:],
2602+ OutputIndex : op .Index ,
2603+ },
2604+ })
2605+ require .NoError (t .t , err )
2606+
2607+ proofFile , err = proof .DecodeFile (proofResp .RawProofFile )
2608+ require .NoError (t .t , err )
2609+ require .Equal (t .t , 4 , proofFile .NumProofs ())
2610+ latestProof , err = proofFile .LastProof ()
2611+ require .NoError (t .t , err )
2612+
2613+ // This proof should contain the STXO exclusion proofs
2614+ stxoProofs = latestProof .InclusionProof .CommitmentProof .STXOProofs
2615+ require .NotNil (t .t , stxoProofs )
2616+ }
2617+
23052618// testCustomChannelsLiquidityEdgeCasesCore is the core logic of the liquidity
23062619// edge cases. This test goes through certain scenarios that expose edge cases
23072620// and behaviors that proved to be buggy in the past and have been directly
0 commit comments