Skip to content

Commit 5369393

Browse files
jharveybguggero
authored andcommitted
itest: test altleaf rejection and merging
1 parent 57c369c commit 5369393

File tree

2 files changed

+218
-0
lines changed

2 files changed

+218
-0
lines changed

itest/psbt_test.go

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -927,6 +927,220 @@ func runPsbtInteractiveSplitSendTest(ctxt context.Context, t *harnessTest,
927927
)
928928
}
929929

930+
// testPsbtInteractiveAltLeafAnchoring tests that the tapfreighter and PSBT flow
931+
// RPC calls will reject a vPkt with invalid AltLeaves. It also tests that
932+
// AltLeaves from multiple vOutputs are merged into one anchor output correctly.
933+
func testPsbtInteractiveAltLeafAnchoring(t *harnessTest) {
934+
// First, we'll make a normal asset with a bunch of units. We're also
935+
// minting a passive asset that should remain where it is.
936+
rpcAssets := MintAssetsConfirmBatch(
937+
t.t, t.lndHarness.Miner().Client, t.tapd,
938+
[]*mintrpc.MintAssetRequest{
939+
issuableAssets[0],
940+
// Our "passive" asset.
941+
{
942+
Asset: &mintrpc.MintAsset{
943+
AssetType: taprpc.AssetType_NORMAL,
944+
Name: "itestbuxx-passive",
945+
AssetMeta: &taprpc.AssetMeta{
946+
Data: []byte("some metadata"),
947+
},
948+
Amount: 123,
949+
},
950+
},
951+
},
952+
)
953+
954+
mintedAsset := rpcAssets[0]
955+
genInfo := rpcAssets[0].AssetGenesis
956+
957+
ctxb := context.Background()
958+
ctxt, cancel := context.WithTimeout(ctxb, defaultWaitTimeout)
959+
defer cancel()
960+
961+
// Now that we have the asset created, we'll make a new node that'll
962+
// serve as the node which'll receive the assets.
963+
secondTapd := setupTapdHarness(
964+
t.t, t, t.lndHarness.Bob, t.universeServer,
965+
)
966+
defer func() {
967+
require.NoError(t.t, secondTapd.stop(!*noDelete))
968+
}()
969+
970+
var (
971+
sender = t.tapd
972+
senderLnd = t.lndHarness.Alice
973+
receiver = secondTapd
974+
id = fn.ToArray[[32]byte](genInfo.AssetId)
975+
partialAmt = mintedAsset.Amount / 4
976+
chainParams = &address.RegressionNetTap
977+
)
978+
979+
// Now, let's create a vPkt for receiving the active asset. We'll use
980+
// two outputs with different script keys and different altLeaves.
981+
receiverScriptKey1, receiverAnchorIntKeyDesc := DeriveKeys(
982+
t.t, receiver,
983+
)
984+
receiverScriptKey1Bytes := receiverScriptKey1.PubKey.
985+
SerializeCompressed()
986+
receiverScriptKey2, _ := DeriveKeys(t.t, receiver)
987+
receiverScriptKey2Bytes := receiverScriptKey2.PubKey.
988+
SerializeCompressed()
989+
990+
vPkt := tappsbt.ForInteractiveSend(
991+
id, partialAmt, receiverScriptKey1, 0, 0, 0,
992+
receiverAnchorIntKeyDesc, asset.V0, chainParams,
993+
)
994+
tappsbt.AddOutput(
995+
vPkt, partialAmt*2, receiverScriptKey2, 0,
996+
receiverAnchorIntKeyDesc, asset.V0,
997+
)
998+
999+
// Let's make multiple sets of altLeaves. Set 1 and 2 should be valid
1000+
// if they were combined, but set 3 is a subset of set 1. If we submit
1001+
// two vPkts with sets 1 and 3, they should be rejected.
1002+
altLeaves1 := asset.RandAltLeaves(t.t, true)
1003+
altLeaves2 := asset.RandAltLeaves(t.t, true)
1004+
altLeaves3 := []*asset.Asset{altLeaves1[0].Copy()}
1005+
1006+
require.NoError(t.t, vPkt.Outputs[0].SetAltLeaves(altLeaves1))
1007+
require.NoError(t.t, vPkt.Outputs[1].SetAltLeaves(altLeaves3))
1008+
1009+
// Packet funding with conflicting altLeaves should fail.
1010+
_, err := maybeFundPacket(t, sender, vPkt)
1011+
require.ErrorContains(t.t, err, asset.ErrDuplicateAltLeafKey.Error())
1012+
1013+
// Let's unset the conflicting altLeaf, sign the vPkt, re-add the
1014+
// conflicting altLeaf, and try to anchor the vPkt.
1015+
vPkt.Outputs[1].AltLeaves = nil
1016+
1017+
fundResp := fundPacket(t, sender, vPkt)
1018+
signActiveResp, err := sender.SignVirtualPsbt(
1019+
ctxt, &wrpc.SignVirtualPsbtRequest{
1020+
FundedPsbt: fundResp.FundedPsbt,
1021+
},
1022+
)
1023+
require.NoError(t.t, err)
1024+
require.Len(t.t, fundResp.PassiveAssetPsbts, 1)
1025+
1026+
signPassiveResp, err := sender.SignVirtualPsbt(
1027+
ctxt, &wrpc.SignVirtualPsbtRequest{
1028+
FundedPsbt: fundResp.PassiveAssetPsbts[0],
1029+
},
1030+
)
1031+
require.NoError(t.t, err)
1032+
1033+
passivevPkt, err := tappsbt.Decode(signPassiveResp.SignedPsbt)
1034+
require.NoError(t.t, err)
1035+
1036+
signedvPkt, err := tappsbt.Decode(signActiveResp.SignedPsbt)
1037+
require.NoError(t.t, err)
1038+
1039+
signedvPktCopy := signedvPkt.Copy()
1040+
require.NoError(t.t, signedvPkt.Outputs[1].SetAltLeaves(altLeaves3))
1041+
signedvPktBytes, err := tappsbt.Encode(signedvPkt)
1042+
require.NoError(t.t, err)
1043+
1044+
// Anchoring this vPkt should fail when creating the Tap commitments for
1045+
// each anchor output.
1046+
_, err = sender.AnchorVirtualPsbts(
1047+
ctxt, &wrpc.AnchorVirtualPsbtsRequest{
1048+
VirtualPsbts: [][]byte{signedvPktBytes},
1049+
},
1050+
)
1051+
require.ErrorContains(t.t, err, asset.ErrDuplicateAltLeafKey.Error())
1052+
1053+
// Trying to anchor the vPkt via the PSBT flow should also fail, for the
1054+
// same reason.
1055+
allPackets := []*tappsbt.VPacket{signedvPkt, passivevPkt}
1056+
btcPacket, err := tapsend.PrepareAnchoringTemplate(allPackets)
1057+
require.NoError(t.t, err)
1058+
1059+
var btcPacketBuf bytes.Buffer
1060+
require.NoError(t.t, btcPacket.Serialize(&btcPacketBuf))
1061+
1062+
commitReq := &wrpc.CommitVirtualPsbtsRequest{
1063+
VirtualPsbts: [][]byte{signedvPktBytes},
1064+
PassiveAssetPsbts: [][]byte{signPassiveResp.SignedPsbt},
1065+
AnchorPsbt: btcPacketBuf.Bytes(),
1066+
Fees: &wrpc.CommitVirtualPsbtsRequest_SatPerVbyte{
1067+
SatPerVbyte: uint64(feeRateSatPerKVByte / 1000),
1068+
},
1069+
AnchorChangeOutput: &wrpc.CommitVirtualPsbtsRequest_Add{
1070+
Add: true,
1071+
},
1072+
}
1073+
_, err = sender.CommitVirtualPsbts(ctxt, commitReq)
1074+
require.ErrorContains(t.t, err, asset.ErrDuplicateAltLeafKey.Error())
1075+
1076+
// Now, let's set non-conflicting altLeaves for the second vOutput, and
1077+
// complete the transfer via the PSBT flow. This should succeed.
1078+
require.NoError(t.t, signedvPktCopy.Outputs[1].SetAltLeaves(altLeaves2))
1079+
signedvPktBytes, err = tappsbt.Encode(signedvPktCopy)
1080+
1081+
require.NoError(t.t, err)
1082+
1083+
commitReq.VirtualPsbts = [][]byte{signedvPktBytes}
1084+
commitResp, err := sender.CommitVirtualPsbts(ctxt, commitReq)
1085+
require.NoError(t.t, err)
1086+
1087+
commitPacket, err := psbt.NewFromRawBytes(
1088+
bytes.NewReader(commitResp.AnchorPsbt), false,
1089+
)
1090+
require.NoError(t.t, err)
1091+
require.Len(t.t, commitResp.VirtualPsbts, 1)
1092+
require.Len(t.t, commitResp.PassiveAssetPsbts, 1)
1093+
1094+
activePacket, err := tappsbt.Decode(commitResp.VirtualPsbts[0])
1095+
require.NoError(t.t, err)
1096+
1097+
passivePacket, err := tappsbt.Decode(commitResp.PassiveAssetPsbts[0])
1098+
require.NoError(t.t, err)
1099+
1100+
commitPacket = signPacket(t.t, senderLnd, commitPacket)
1101+
commitPacket = FinalizePacket(t.t, senderLnd.RPC, commitPacket)
1102+
publishResp := LogAndPublish(
1103+
t.t, sender, commitPacket, []*tappsbt.VPacket{activePacket},
1104+
[]*tappsbt.VPacket{passivePacket}, commitResp,
1105+
)
1106+
1107+
expectedAmounts := []uint64{
1108+
partialAmt, partialAmt * 2, partialAmt,
1109+
}
1110+
ConfirmAndAssertOutboundTransferWithOutputs(
1111+
t.t, t.lndHarness.Miner().Client, sender, publishResp,
1112+
genInfo.AssetId, expectedAmounts, 0, 1, len(expectedAmounts),
1113+
)
1114+
1115+
// This is an interactive transfer, so we do need to manually send the
1116+
// proofs from the sender to the receiver.
1117+
_ = sendProof(
1118+
t, sender, receiver, publishResp, receiverScriptKey1Bytes,
1119+
genInfo,
1120+
)
1121+
_ = sendProof(
1122+
t, sender, receiver, publishResp, receiverScriptKey2Bytes,
1123+
genInfo,
1124+
)
1125+
1126+
// Now, both output proofs should contain altLeaf sets 1 and 2, since
1127+
// our two vOutputs should be committed in the same anchor output.
1128+
receiverAssets, err := receiver.ListAssets(
1129+
ctxt, &taprpc.ListAssetRequest{},
1130+
)
1131+
require.NoError(t.t, err)
1132+
1133+
allAltLeaves := append(altLeaves1, altLeaves2...)
1134+
leafMap := map[string][]*asset.Asset{
1135+
string(receiverScriptKey1Bytes): allAltLeaves,
1136+
string(receiverScriptKey2Bytes): allAltLeaves,
1137+
}
1138+
1139+
for _, asset := range receiverAssets.Assets {
1140+
AssertProofAltLeaves(t.t, receiver, asset, leafMap)
1141+
}
1142+
}
1143+
9301144
// testPsbtInteractiveTapscriptSibling tests that we can send assets to an
9311145
// anchor output that also commits to a tapscript sibling.
9321146
func testPsbtInteractiveTapscriptSibling(t *harnessTest) {

itest/test_list_on_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,10 @@ var testCases = []*testCase{
253253
name: "multi input psbt single asset id",
254254
test: testMultiInputPsbtSingleAssetID,
255255
},
256+
{
257+
name: "psbt alt leaf anchoring",
258+
test: testPsbtInteractiveAltLeafAnchoring,
259+
},
256260
{
257261
name: "universe REST API",
258262
test: testUniverseREST,

0 commit comments

Comments
 (0)