Skip to content

Commit 496c5e3

Browse files
authored
Merge pull request #10257 from ziggie1984/make-verifyAttempt-robust
Make verifyAttempt more robust
2 parents d0ac5af + f4816c1 commit 496c5e3

File tree

3 files changed

+273
-0
lines changed

3 files changed

+273
-0
lines changed

payments/db/errors.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@ var (
7878
ErrBlindedPaymentTotalAmountMismatch = errors.New("blinded path " +
7979
"total amount mismatch")
8080

81+
// ErrMixedBlindedAndNonBlindedPayments is returned if we try to
82+
// register a non-blinded attempt to a payment which uses a blinded
83+
// paths or vice versa.
84+
ErrMixedBlindedAndNonBlindedPayments = errors.New("mixed blinded and " +
85+
"non-blinded payments")
86+
8187
// ErrMPPPaymentAddrMismatch is returned if we try to register an MPP
8288
// shard where the payment address doesn't match existing shards.
8389
ErrMPPPaymentAddrMismatch = errors.New("payment address mismatch")

payments/db/payment.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,13 +755,21 @@ func verifyAttempt(payment *MPPayment, attempt *HTLCAttemptInfo) error {
755755

756756
for _, h := range payment.InFlightHTLCs() {
757757
hMpp := h.Route.FinalHop().MPP
758+
hBlinded := len(h.Route.FinalHop().EncryptedData) != 0
758759

759760
// If this is a blinded payment, then no existing HTLCs
760761
// should have MPP records.
761762
if isBlinded && hMpp != nil {
762763
return ErrMPPRecordInBlindedPayment
763764
}
764765

766+
// If the payment is blinded (previous attempts used blinded
767+
// paths) and the attempt is not, or vice versa, return an
768+
// error.
769+
if isBlinded != hBlinded {
770+
return ErrMixedBlindedAndNonBlindedPayments
771+
}
772+
765773
// If this is a blinded payment, then we just need to
766774
// check that the TotalAmtMsat field for this shard
767775
// is equal to that of any other shard in the same

payments/db/payment_test.go

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,6 +1178,265 @@ func makeAttemptInfo(total, amtForwarded int) HTLCAttemptInfo {
11781178
}
11791179
}
11801180

1181+
// lastHopArgs is a helper struct that holds the arguments for the last hop
1182+
// when creating an attempt with a route with a single hop (last hop).
1183+
type lastHopArgs struct {
1184+
amt lnwire.MilliSatoshi
1185+
total lnwire.MilliSatoshi
1186+
mpp *record.MPP
1187+
encrypted []byte
1188+
}
1189+
1190+
// makeLastHopAttemptInfo creates an HTLCAttemptInfo with a route with a single
1191+
// hop (last hop).
1192+
func makeLastHopAttemptInfo(id uint64, args lastHopArgs) HTLCAttemptInfo {
1193+
lastHop := &route.Hop{
1194+
PubKeyBytes: vertex,
1195+
ChannelID: 1,
1196+
AmtToForward: args.amt,
1197+
MPP: args.mpp,
1198+
EncryptedData: args.encrypted,
1199+
TotalAmtMsat: args.total,
1200+
}
1201+
1202+
return HTLCAttemptInfo{
1203+
AttemptID: id,
1204+
Route: route.Route{
1205+
SourcePubKey: vertex,
1206+
TotalAmount: args.amt,
1207+
Hops: []*route.Hop{lastHop},
1208+
},
1209+
}
1210+
}
1211+
1212+
// makePayment creates an MPPayment with set of attempts.
1213+
func makePayment(total lnwire.MilliSatoshi,
1214+
attempts ...HTLCAttempt) *MPPayment {
1215+
1216+
return &MPPayment{
1217+
Info: &PaymentCreationInfo{
1218+
Value: total,
1219+
},
1220+
HTLCs: attempts,
1221+
}
1222+
}
1223+
1224+
// TestVerifyAttemptNonMPPAmountMismatch tests that we return an error if the
1225+
// attempted amount doesn't match the payment amount.
1226+
func TestVerifyAttemptNonMPPAmountMismatch(t *testing.T) {
1227+
t.Parallel()
1228+
1229+
payment := makePayment(1000)
1230+
attempt := makeLastHopAttemptInfo(1, lastHopArgs{amt: 900})
1231+
1232+
require.ErrorIs(t, verifyAttempt(payment, &attempt), ErrValueMismatch)
1233+
}
1234+
1235+
// TestVerifyAttemptNonMPPSuccess tests that we don't return an error if the
1236+
// attempted amount matches the payment amount.
1237+
func TestVerifyAttemptNonMPPSuccess(t *testing.T) {
1238+
t.Parallel()
1239+
1240+
payment := makePayment(1200)
1241+
attempt := makeLastHopAttemptInfo(1, lastHopArgs{amt: 1200})
1242+
1243+
require.NoError(t, verifyAttempt(payment, &attempt))
1244+
}
1245+
1246+
// TestVerifyAttemptMPPTransitionErrors tests cases where we cannot transition
1247+
// from a non-MPP payment to an MPP payment or vice versa.
1248+
func TestVerifyAttemptMPPTransitionErrors(t *testing.T) {
1249+
t.Parallel()
1250+
1251+
total := lnwire.MilliSatoshi(2000)
1252+
mpp := record.NewMPP(total, testHash)
1253+
1254+
paymentWithMPP := makePayment(
1255+
total,
1256+
HTLCAttempt{
1257+
HTLCAttemptInfo: makeLastHopAttemptInfo(
1258+
1,
1259+
lastHopArgs{amt: 1000, mpp: mpp},
1260+
),
1261+
},
1262+
)
1263+
nonMPP := makeLastHopAttemptInfo(2, lastHopArgs{amt: 1000})
1264+
require.ErrorIs(t, verifyAttempt(paymentWithMPP, &nonMPP), ErrMPPayment)
1265+
1266+
paymentWithNonMPP := makePayment(
1267+
total,
1268+
HTLCAttempt{
1269+
HTLCAttemptInfo: makeLastHopAttemptInfo(
1270+
1,
1271+
lastHopArgs{amt: total},
1272+
),
1273+
},
1274+
)
1275+
mppAttempt := makeLastHopAttemptInfo(
1276+
2, lastHopArgs{amt: 1000, mpp: mpp},
1277+
)
1278+
require.ErrorIs(
1279+
t,
1280+
verifyAttempt(paymentWithNonMPP, &mppAttempt),
1281+
ErrNonMPPayment,
1282+
)
1283+
}
1284+
1285+
// TestVerifyAttemptMPPOptionMismatch tests that we return an error if the
1286+
// MPP options don't match the payment options.
1287+
func TestVerifyAttemptMPPOptionMismatch(t *testing.T) {
1288+
t.Parallel()
1289+
1290+
total := lnwire.MilliSatoshi(3000)
1291+
goodMPP := record.NewMPP(total, testHash)
1292+
payment := makePayment(
1293+
total,
1294+
HTLCAttempt{
1295+
HTLCAttemptInfo: makeLastHopAttemptInfo(
1296+
1,
1297+
lastHopArgs{amt: 1500, mpp: goodMPP},
1298+
),
1299+
},
1300+
)
1301+
1302+
badAddr := record.NewMPP(total, rev)
1303+
attemptBadAddr := makeLastHopAttemptInfo(
1304+
2,
1305+
lastHopArgs{amt: 1500, mpp: badAddr},
1306+
)
1307+
require.ErrorIs(
1308+
t,
1309+
verifyAttempt(payment, &attemptBadAddr),
1310+
ErrMPPPaymentAddrMismatch,
1311+
)
1312+
1313+
badTotal := record.NewMPP(total-1, testHash)
1314+
attemptBadTotal := makeLastHopAttemptInfo(
1315+
3,
1316+
lastHopArgs{amt: 1500, mpp: badTotal},
1317+
)
1318+
require.ErrorIs(
1319+
t,
1320+
verifyAttempt(payment, &attemptBadTotal),
1321+
ErrMPPTotalAmountMismatch,
1322+
)
1323+
1324+
matching := makeLastHopAttemptInfo(
1325+
4,
1326+
lastHopArgs{amt: 1500, mpp: record.NewMPP(total, testHash)},
1327+
)
1328+
require.NoError(t, verifyAttempt(payment, &matching))
1329+
}
1330+
1331+
// TestVerifyAttemptBlindedValidation tests that we return an error if we try
1332+
// to register an MPP attempt for a blinded payment.
1333+
func TestVerifyAttemptBlindedValidation(t *testing.T) {
1334+
t.Parallel()
1335+
1336+
total := lnwire.MilliSatoshi(5000)
1337+
1338+
// Payment with a blinded attempt.
1339+
existing := makeLastHopAttemptInfo(
1340+
1,
1341+
lastHopArgs{amt: 2500, total: total, encrypted: []byte{1}},
1342+
)
1343+
payment := makePayment(
1344+
total,
1345+
HTLCAttempt{HTLCAttemptInfo: existing},
1346+
)
1347+
1348+
// Attempt with a normal MPP record should fail because a payment
1349+
// cannot have a mix of blinded and non-blinded attempts.
1350+
goodMPP := makeLastHopAttemptInfo(
1351+
2,
1352+
lastHopArgs{amt: 2500, mpp: record.NewMPP(total, testHash)},
1353+
)
1354+
require.ErrorIs(
1355+
t, verifyAttempt(payment, &goodMPP),
1356+
ErrMixedBlindedAndNonBlindedPayments,
1357+
)
1358+
1359+
blindedMPP := makeLastHopAttemptInfo(
1360+
2,
1361+
lastHopArgs{
1362+
amt: 2500,
1363+
total: total,
1364+
mpp: record.NewMPP(total, testHash),
1365+
encrypted: []byte{2},
1366+
},
1367+
)
1368+
require.ErrorIs(
1369+
t,
1370+
verifyAttempt(payment, &blindedMPP),
1371+
ErrMPPRecordInBlindedPayment,
1372+
)
1373+
1374+
mismatchedTotal := makeLastHopAttemptInfo(
1375+
3,
1376+
lastHopArgs{amt: 2500, total: total + 1, encrypted: []byte{3}},
1377+
)
1378+
require.ErrorIs(
1379+
t,
1380+
verifyAttempt(payment, &mismatchedTotal),
1381+
ErrBlindedPaymentTotalAmountMismatch,
1382+
)
1383+
1384+
matching := makeLastHopAttemptInfo(
1385+
4,
1386+
lastHopArgs{amt: 2500, total: total, encrypted: []byte{4}},
1387+
)
1388+
require.NoError(t, verifyAttempt(payment, &matching))
1389+
}
1390+
1391+
// TestVerifyAttemptBlindedMixedWithNonBlinded tests that we return an error if
1392+
// we try to register a non-MPP attempt for a blinded payment.
1393+
func TestVerifyAttemptBlindedMixedWithNonBlinded(t *testing.T) {
1394+
t.Parallel()
1395+
1396+
total := lnwire.MilliSatoshi(4000)
1397+
1398+
// Payment with a blinded attempt.
1399+
existing := makeLastHopAttemptInfo(
1400+
1,
1401+
lastHopArgs{amt: 2000, total: total, encrypted: []byte{1}},
1402+
)
1403+
payment := makePayment(
1404+
total,
1405+
HTLCAttempt{HTLCAttemptInfo: existing},
1406+
)
1407+
1408+
partial := makeLastHopAttemptInfo(2, lastHopArgs{amt: 2000})
1409+
require.ErrorIs(
1410+
t,
1411+
verifyAttempt(payment, &partial),
1412+
ErrMixedBlindedAndNonBlindedPayments,
1413+
)
1414+
1415+
full := makeLastHopAttemptInfo(3, lastHopArgs{amt: total})
1416+
require.ErrorIs(
1417+
t,
1418+
verifyAttempt(payment, &full),
1419+
ErrMixedBlindedAndNonBlindedPayments,
1420+
)
1421+
}
1422+
1423+
// TestVerifyAttemptAmountExceedsTotal tests that we return an error if the
1424+
// attempted amount exceeds the payment amount.
1425+
func TestVerifyAttemptAmountExceedsTotal(t *testing.T) {
1426+
t.Parallel()
1427+
1428+
total := lnwire.MilliSatoshi(1000)
1429+
mpp := record.NewMPP(total, testHash)
1430+
existing := makeLastHopAttemptInfo(1, lastHopArgs{amt: 800, mpp: mpp})
1431+
payment := makePayment(
1432+
total,
1433+
HTLCAttempt{HTLCAttemptInfo: existing},
1434+
)
1435+
1436+
attempt := makeLastHopAttemptInfo(2, lastHopArgs{amt: 300, mpp: mpp})
1437+
require.ErrorIs(t, verifyAttempt(payment, &attempt), ErrValueExceedsAmt)
1438+
}
1439+
11811440
// TestEmptyRoutesGenerateSphinxPacket tests that the generateSphinxPacket
11821441
// function is able to gracefully handle being passed a nil set of hops for the
11831442
// route by the caller.

0 commit comments

Comments
 (0)