@@ -5,19 +5,23 @@ import (
55 "context"
66 "fmt"
77 "strings"
8+ "testing"
89 "time"
910
1011 "github.com/btcsuite/btcd/btcec/v2"
12+ "github.com/btcsuite/btcd/btcec/v2/schnorr"
1113 "github.com/btcsuite/btcd/chaincfg/chainhash"
1214 "github.com/btcsuite/btcd/wire"
1315 taprootassets "github.com/lightninglabs/taproot-assets"
16+ "github.com/lightninglabs/taproot-assets/asset"
1417 "github.com/lightninglabs/taproot-assets/fn"
1518 "github.com/lightninglabs/taproot-assets/itest/rpcassert"
1619 "github.com/lightninglabs/taproot-assets/mssmt"
1720 "github.com/lightninglabs/taproot-assets/tapgarden"
1821 "github.com/lightninglabs/taproot-assets/taprpc"
1922 "github.com/lightninglabs/taproot-assets/taprpc/mintrpc"
2023 unirpc "github.com/lightninglabs/taproot-assets/taprpc/universerpc"
24+ "github.com/lightninglabs/taproot-assets/universe"
2125 "github.com/lightninglabs/taproot-assets/universe/supplycommit"
2226 "github.com/stretchr/testify/require"
2327)
@@ -1032,6 +1036,356 @@ func testSupplyCommitMintBurn(t *harnessTest) {
10321036 t .Log ("Supply commit mint and burn test completed successfully" )
10331037}
10341038
1039+ // testFetchSupplyLeaves tests the FetchSupplyLeaves RPC endpoint by:
1040+ //
1041+ // 1. Minting an asset group with supply commitments enabled.
1042+ // 2. Calling FetchSupplyLeaves to verify initial mint leaves.
1043+ // 3. Burning some of the asset and updating the supply commit.
1044+ // 4. Calling FetchSupplyLeaves to verify burn leaves are included.
1045+ // 5. Minting another tranche into the same group.
1046+ // 6. Calling FetchSupplyLeaves to verify all leaves are present.
1047+ // 7. Testing inclusion proof generation for various leaf types.
1048+ func testFetchSupplyLeaves (t * harnessTest ) {
1049+ ctxb := context .Background ()
1050+
1051+ t .Log ("Minting initial asset group with supply commitments enabled" )
1052+ mintReq := CopyRequest (issuableAssets [0 ])
1053+ mintReq .Asset .Amount = 8000
1054+
1055+ rpcFirstAsset , _ := MintAssetWithSupplyCommit (
1056+ t , mintReq , fn .None [btcec.PublicKey ](),
1057+ )
1058+
1059+ groupKeyBytes := rpcFirstAsset .AssetGroup .TweakedGroupKey
1060+ require .NotNil (t .t , groupKeyBytes )
1061+
1062+ t .Log ("Creating first supply commitment transaction" )
1063+ UpdateAndMineSupplyCommit (
1064+ t .t , ctxb , t .tapd , t .lndHarness .Miner ().Client ,
1065+ groupKeyBytes , 1 ,
1066+ )
1067+
1068+ t .Log ("Waiting for first supply commitment to be mined" )
1069+ _ , supplyOutpoint := WaitForSupplyCommit (
1070+ t .t , ctxb , t .tapd , groupKeyBytes , fn .None [wire.OutPoint ](),
1071+ func (resp * unirpc.FetchSupplyCommitResponse ) bool {
1072+ return resp .ChainData .BlockHeight > 0 &&
1073+ len (resp .ChainData .BlockHash ) > 0
1074+ },
1075+ )
1076+
1077+ t .Log ("Fetching supply leaves after initial mint" )
1078+ req := unirpc.FetchSupplyLeavesRequest {
1079+ GroupKey : & unirpc.FetchSupplyLeavesRequest_GroupKeyBytes {
1080+ GroupKeyBytes : groupKeyBytes ,
1081+ },
1082+ }
1083+ leavesResp1 , err := t .tapd .FetchSupplyLeaves (ctxb , & req )
1084+ require .NoError (t .t , err )
1085+ require .NotNil (t .t , leavesResp1 )
1086+
1087+ // Verify we have one issuance leaf and no burn/ignore leaves.
1088+ require .Len (
1089+ t .t , leavesResp1 .IssuanceLeaves , 1 ,
1090+ "expected 1 issuance leaf after first mint" ,
1091+ )
1092+ require .Len (
1093+ t .t , leavesResp1 .BurnLeaves , 0 ,
1094+ "expected 0 burn leaves after first mint" ,
1095+ )
1096+ require .Len (
1097+ t .t , leavesResp1 .IgnoreLeaves , 0 ,
1098+ "expected 0 ignore leaves after first mint" ,
1099+ )
1100+
1101+ // Verify the issuance leaf amount.
1102+ issuanceLeaf1 := leavesResp1 .IssuanceLeaves [0 ]
1103+ require .EqualValues (
1104+ t .t , mintReq .Asset .Amount , issuanceLeaf1 .LeafNode .RootSum ,
1105+ "issuance leaf amount mismatch" ,
1106+ )
1107+
1108+ t .Log ("Burning portion of the asset" )
1109+ const (
1110+ burnAmt = 1500
1111+ burnNote = "FetchSupplyLeaves burn test"
1112+ )
1113+
1114+ burnResp , err := t .tapd .BurnAsset (ctxb , & taprpc.BurnAssetRequest {
1115+ Asset : & taprpc.BurnAssetRequest_AssetId {
1116+ AssetId : rpcFirstAsset .AssetGenesis .AssetId ,
1117+ },
1118+ AmountToBurn : burnAmt ,
1119+ Note : burnNote ,
1120+ ConfirmationText : taprootassets .AssetBurnConfirmationText ,
1121+ })
1122+ require .NoError (t .t , err )
1123+ require .NotNil (t .t , burnResp )
1124+
1125+ t .Log ("Confirming burn transaction" )
1126+ AssertAssetOutboundTransferWithOutputs (
1127+ t .t , t .lndHarness .Miner ().Client , t .tapd , burnResp .BurnTransfer ,
1128+ [][]byte {rpcFirstAsset .AssetGenesis .AssetId },
1129+ []uint64 {mintReq .Asset .Amount - burnAmt , burnAmt },
1130+ 0 , 1 , 2 , true ,
1131+ )
1132+
1133+ t .Log ("Updating supply commitment after burn" )
1134+ UpdateAndMineSupplyCommit (
1135+ t .t , ctxb , t .tapd , t .lndHarness .Miner ().Client ,
1136+ groupKeyBytes , 1 ,
1137+ )
1138+
1139+ // Wait for the supply commitment to include the burn.
1140+ _ , supplyOutpoint = WaitForSupplyCommit (
1141+ t .t , ctxb , t .tapd , groupKeyBytes , fn .Some (supplyOutpoint ),
1142+ func (resp * unirpc.FetchSupplyCommitResponse ) bool {
1143+ if resp .BurnSubtreeRoot == nil {
1144+ return false
1145+ }
1146+
1147+ actualBurnSum := resp .BurnSubtreeRoot .RootNode .RootSum
1148+ return actualBurnSum == int64 (burnAmt )
1149+ },
1150+ )
1151+
1152+ t .Log ("Fetching supply leaves after burn" )
1153+ req = unirpc.FetchSupplyLeavesRequest {
1154+ GroupKey : & unirpc.FetchSupplyLeavesRequest_GroupKeyBytes {
1155+ GroupKeyBytes : groupKeyBytes ,
1156+ },
1157+ }
1158+ leavesResp2 , err := t .tapd .FetchSupplyLeaves (ctxb , & req )
1159+ require .NoError (t .t , err )
1160+ require .NotNil (t .t , leavesResp2 )
1161+
1162+ // Verify we have one issuance leaf and one burn leaf.
1163+ require .Len (
1164+ t .t , leavesResp2 .IssuanceLeaves , 1 ,
1165+ "expected 1 issuance leaf after burn" ,
1166+ )
1167+ require .Len (
1168+ t .t , leavesResp2 .BurnLeaves , 1 ,
1169+ "expected 1 burn leaf after burn" ,
1170+ )
1171+ require .Len (
1172+ t .t , leavesResp2 .IgnoreLeaves , 0 ,
1173+ "expected 0 ignore leaves after burn" ,
1174+ )
1175+
1176+ burnLeaf := leavesResp2 .BurnLeaves [0 ]
1177+ require .EqualValues (t .t , burnAmt , burnLeaf .LeafNode .RootSum ,
1178+ "burn leaf amount mismatch" )
1179+
1180+ t .Log ("Minting second tranche into the same asset group" )
1181+ secondMintReq := & mintrpc.MintAssetRequest {
1182+ Asset : & mintrpc.MintAsset {
1183+ AssetType : taprpc .AssetType_NORMAL ,
1184+ Name : "itestbuxx-fetchsupplyleaves-tranche-2" ,
1185+ AssetMeta : & taprpc.AssetMeta {
1186+ Data : []byte ("second tranche for " +
1187+ "FetchSupplyLeaves test" ),
1188+ },
1189+ Amount : 3500 ,
1190+ AssetVersion : taprpc .AssetVersion_ASSET_VERSION_V1 ,
1191+ NewGroupedAsset : false ,
1192+ GroupedAsset : true ,
1193+ GroupKey : groupKeyBytes ,
1194+
1195+ EnableSupplyCommitments : true ,
1196+ },
1197+ }
1198+
1199+ rpcSecondAsset := MintAssetsConfirmBatch (
1200+ t .t , t .lndHarness .Miner ().Client , t .tapd ,
1201+ []* mintrpc.MintAssetRequest {secondMintReq },
1202+ )
1203+ require .Len (t .t , rpcSecondAsset , 1 , "expected one minted asset" )
1204+ require .EqualValues (
1205+ t .t , groupKeyBytes ,
1206+ rpcSecondAsset [0 ].AssetGroup .TweakedGroupKey ,
1207+ )
1208+
1209+ t .Log ("Updating supply commitment after second mint" )
1210+ UpdateAndMineSupplyCommit (
1211+ t .t , ctxb , t .tapd , t .lndHarness .Miner ().Client ,
1212+ groupKeyBytes , 1 ,
1213+ )
1214+
1215+ // Wait for the supply commitment to include both mints.
1216+ expectedIssuanceTotal := int64 (
1217+ mintReq .Asset .Amount + secondMintReq .Asset .Amount ,
1218+ )
1219+ _ , _ = WaitForSupplyCommit (
1220+ t .t , ctxb , t .tapd , groupKeyBytes , fn .Some (supplyOutpoint ),
1221+ func (resp * unirpc.FetchSupplyCommitResponse ) bool {
1222+ return resp .IssuanceSubtreeRoot != nil &&
1223+ resp .IssuanceSubtreeRoot .RootNode .RootSum ==
1224+ expectedIssuanceTotal
1225+ },
1226+ )
1227+
1228+ t .Log ("Fetching supply leaves after second mint" )
1229+ req = unirpc.FetchSupplyLeavesRequest {
1230+ GroupKey : & unirpc.FetchSupplyLeavesRequest_GroupKeyBytes {
1231+ GroupKeyBytes : groupKeyBytes ,
1232+ },
1233+ }
1234+ leavesResp3 , err := t .tapd .FetchSupplyLeaves (ctxb , & req )
1235+ require .NoError (t .t , err )
1236+ require .NotNil (t .t , leavesResp3 )
1237+
1238+ // Verify we have two issuance leaves and one burn leaf.
1239+ require .Len (
1240+ t .t , leavesResp3 .IssuanceLeaves , 2 ,
1241+ "expected 2 issuance leaves after second mint" ,
1242+ )
1243+ require .Len (
1244+ t .t , leavesResp3 .BurnLeaves , 1 ,
1245+ "expected 1 burn leaf after second mint" ,
1246+ )
1247+ require .Len (
1248+ t .t , leavesResp3 .IgnoreLeaves , 0 ,
1249+ "expected 0 ignore leaves after second mint" ,
1250+ )
1251+
1252+ // Verify the total issuance amount across both leaves.
1253+ totalIssuanceAmount := int64 (0 )
1254+ for _ , leaf := range leavesResp3 .IssuanceLeaves {
1255+ totalIssuanceAmount += leaf .LeafNode .RootSum
1256+ }
1257+ require .EqualValues (
1258+ t .t , expectedIssuanceTotal , totalIssuanceAmount ,
1259+ "total issuance amount mismatch" ,
1260+ )
1261+
1262+ t .Log ("Testing inclusion proof generation for supply leaves" )
1263+
1264+ // Collect leaf keys for inclusion proof request.
1265+ var issuanceLeafKeys [][]byte
1266+ var burnLeafKeys [][]byte
1267+ for _ , leaf := range leavesResp3 .IssuanceLeaves {
1268+ issuanceLeafKeys = append (
1269+ issuanceLeafKeys ,
1270+ unmarshalRPCSupplyLeafKey (t .t , leaf .LeafKey ),
1271+ )
1272+ }
1273+ for _ , leaf := range leavesResp3 .BurnLeaves {
1274+ burnLeafKeys = append (
1275+ burnLeafKeys ,
1276+ unmarshalRPCSupplyLeafKey (t .t , leaf .LeafKey ),
1277+ )
1278+ }
1279+
1280+ // Request supply leaves with inclusion proofs.
1281+ req = unirpc.FetchSupplyLeavesRequest {
1282+ GroupKey : & unirpc.FetchSupplyLeavesRequest_GroupKeyBytes {
1283+ GroupKeyBytes : groupKeyBytes ,
1284+ },
1285+ IssuanceLeafKeys : issuanceLeafKeys ,
1286+ BurnLeafKeys : burnLeafKeys ,
1287+ }
1288+ leavesRespWithProofs , err := t .tapd .FetchSupplyLeaves (ctxb , & req )
1289+ require .NoError (t .t , err )
1290+ require .NotNil (t .t , leavesRespWithProofs )
1291+
1292+ // Verify that inclusion proofs are provided.
1293+ require .Len (
1294+ t .t , leavesRespWithProofs .IssuanceLeafInclusionProofs ,
1295+ len (issuanceLeafKeys ),
1296+ "expected inclusion proofs for all issuance leaf keys" ,
1297+ )
1298+ require .Len (
1299+ t .t , leavesRespWithProofs .BurnLeafInclusionProofs ,
1300+ len (burnLeafKeys ),
1301+ "expected inclusion proofs for all burn leaf keys" ,
1302+ )
1303+
1304+ t .Log ("Verifying inclusion proof validity" )
1305+
1306+ // Fetch the current supply commitment to get the subtree roots.
1307+ reqFetchCommit := unirpc.FetchSupplyCommitRequest {
1308+ GroupKey : & unirpc.FetchSupplyCommitRequest_GroupKeyBytes {
1309+ GroupKeyBytes : groupKeyBytes ,
1310+ },
1311+ Locator : & unirpc.FetchSupplyCommitRequest_Latest {
1312+ Latest : true ,
1313+ },
1314+ }
1315+ fetchResp , err := t .tapd .FetchSupplyCommit (ctxb , & reqFetchCommit )
1316+ require .NoError (t .t , err )
1317+ require .NotNil (t .t , fetchResp )
1318+
1319+ // Verify issuance leaf inclusion proofs.
1320+ inclusionProofs := leavesRespWithProofs .IssuanceLeafInclusionProofs
1321+ for i , proofBytes := range inclusionProofs {
1322+ leafKey := fn.ToArray [[32 ]byte ](issuanceLeafKeys [i ])
1323+ leafNode := unmarshalMerkleSumNode (
1324+ leavesRespWithProofs .IssuanceLeaves [i ].LeafNode ,
1325+ )
1326+
1327+ expectedSubtreeRootHash := fn.ToArray [[32 ]byte ](
1328+ fetchResp .IssuanceSubtreeRoot .RootNode .RootHash ,
1329+ )
1330+
1331+ AssertInclusionProof (
1332+ t , expectedSubtreeRootHash , proofBytes ,
1333+ leafKey , leafNode ,
1334+ )
1335+ }
1336+
1337+ // Verify burn leaf inclusion proofs.
1338+ inclusionProofs = leavesRespWithProofs .BurnLeafInclusionProofs
1339+ for i , proofBytes := range inclusionProofs {
1340+ leafKey := fn.ToArray [[32 ]byte ](burnLeafKeys [i ])
1341+ leafNode := unmarshalMerkleSumNode (
1342+ leavesRespWithProofs .BurnLeaves [i ].LeafNode ,
1343+ )
1344+
1345+ expectedSubtreeRootHash := fn.ToArray [[32 ]byte ](
1346+ fetchResp .BurnSubtreeRoot .RootNode .RootHash ,
1347+ )
1348+
1349+ AssertInclusionProof (
1350+ t , expectedSubtreeRootHash , proofBytes ,
1351+ leafKey , leafNode ,
1352+ )
1353+ }
1354+ }
1355+
1356+ // unmarshalRPCSupplyLeafKey converts a *unirpc.SupplyLeafKey to a byte slice
1357+ // using the same method as the universe key serialization.
1358+ func unmarshalRPCSupplyLeafKey (t * testing.T ,
1359+ leafKey * unirpc.SupplyLeafKey ) []byte {
1360+
1361+ t .Helper ()
1362+
1363+ hash , err := chainhash .NewHashFromStr (leafKey .Outpoint .HashStr )
1364+ require .NoError (t , err )
1365+
1366+ outpoint := wire.OutPoint {
1367+ Hash : * hash ,
1368+ Index : uint32 (leafKey .Outpoint .Index ),
1369+ }
1370+
1371+ scriptKeyPubKey , err := schnorr .ParsePubKey (leafKey .ScriptKey )
1372+ require .NoError (t , err )
1373+
1374+ scriptKey := asset .NewScriptKey (scriptKeyPubKey )
1375+
1376+ assetID := fn.ToArray [[32 ]byte ](leafKey .AssetId )
1377+ assetLeafKey := universe.AssetLeafKey {
1378+ BaseLeafKey : universe.BaseLeafKey {
1379+ OutPoint : outpoint ,
1380+ ScriptKey : & scriptKey ,
1381+ },
1382+ AssetID : assetID ,
1383+ }
1384+
1385+ universeKey := assetLeafKey .UniverseKey ()
1386+ return universeKey [:]
1387+ }
1388+
10351389// testSupplyVerifyPeerNode verifies that a secondary node can sync and fetch
10361390// multiple supply commitments published by the primary node. It:
10371391//
0 commit comments