Handle interception probing payments

probing payments uses a probing payment hash which is:
sha256("probing-01:" || payment_hash).

When the interceptor detects such a hash for a payment which is supposed
to trigger a channel creation , it checks if the destination is online,
and if online, fails with INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS error in
order to let the payer knows that the payment would be successful.
This commit is contained in:
Yaacov Akiba Slama
2020-11-08 17:46:26 +02:00
parent faff8f60a9
commit f407ec9e9c
5 changed files with 53 additions and 20 deletions

20
db.go
View File

@@ -24,25 +24,25 @@ func pgConnect() error {
return nil return nil
} }
func paymentInfo(paymentHash []byte) ([]byte, []byte, int64, int64, []byte, uint32, error) { func paymentInfo(htlcPaymentHash []byte) ([]byte, []byte, []byte, int64, int64, []byte, uint32, error) {
var ( var (
paymentSecret, destination []byte paymentHash, paymentSecret, destination []byte
incomingAmountMsat, outgoingAmountMsat int64 incomingAmountMsat, outgoingAmountMsat int64
fundingTxID []byte fundingTxID []byte
fundingTxOutnum pgtype.Int4 fundingTxOutnum pgtype.Int4
) )
err := pgxPool.QueryRow(context.Background(), err := pgxPool.QueryRow(context.Background(),
`SELECT payment_secret, destination, incoming_amount_msat, outgoing_amount_msat, funding_tx_id, funding_tx_outnum `SELECT payment_hash, payment_secret, destination, incoming_amount_msat, outgoing_amount_msat, funding_tx_id, funding_tx_outnum
FROM payments FROM payments
WHERE payment_hash=$1`, WHERE payment_hash=$1 OR sha256('probing-01:' || payment_hash)=$1`,
paymentHash).Scan(&paymentSecret, &destination, &incomingAmountMsat, &outgoingAmountMsat, &fundingTxID, &fundingTxOutnum) htlcPaymentHash).Scan(&paymentHash, &paymentSecret, &destination, &incomingAmountMsat, &outgoingAmountMsat, &fundingTxID, &fundingTxOutnum)
if err != nil { if err != nil {
if err == pgx.ErrNoRows { if err == pgx.ErrNoRows {
err = nil err = nil
} }
return nil, nil, 0, 0, nil, 0, err return nil, nil, nil, 0, 0, nil, 0, err
} }
return paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, fundingTxID, uint32(fundingTxOutnum.Int), nil return paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, fundingTxID, uint32(fundingTxOutnum.Int), nil
} }
func setFundingTx(paymentHash, fundingTxID []byte, fundingTxOutnum int) error { func setFundingTx(paymentHash, fundingTxID []byte, fundingTxOutnum int) error {

8
go.mod
View File

@@ -4,16 +4,16 @@ go 1.14
require ( require (
github.com/aws/aws-sdk-go v1.30.20 github.com/aws/aws-sdk-go v1.30.20
github.com/btcsuite/btcd v0.20.1-beta github.com/btcsuite/btcd v0.20.1-beta.0.20200730232343-1db1b6f8217f
github.com/caddyserver/certmagic v0.11.2 github.com/caddyserver/certmagic v0.11.2
github.com/golang/protobuf v1.4.2 github.com/golang/protobuf v1.4.2
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0
github.com/jackc/pgtype v1.4.2 github.com/jackc/pgtype v1.4.2
github.com/jackc/pgx/v4 v4.8.1 github.com/jackc/pgx/v4 v4.8.1
github.com/lightningnetwork/lightning-onion v1.0.1 github.com/lightningnetwork/lightning-onion v1.0.2-0.20200501022730-3c8c8d0b89ea
github.com/lightningnetwork/lnd v0.10.0-beta github.com/lightningnetwork/lnd v0.11.0-beta
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
google.golang.org/grpc v1.31.0 google.golang.org/grpc v1.31.0
) )
replace github.com/lightningnetwork/lnd v0.10.0-beta => github.com/breez/lnd v0.10.0-beta.rc6.0.20200727142715-f67a1052c0e0 replace github.com/lightningnetwork/lnd v0.11.0-beta => github.com/breez/lnd v0.11.0-beta.rc4.0.20201101122458-227226f00b18

View File

@@ -3,6 +3,7 @@ package main
import ( import (
"bytes" "bytes"
"context" "context"
"encoding/hex"
"fmt" "fmt"
"log" "log"
"math/big" "math/big"
@@ -32,6 +33,23 @@ func checkPayment(incomingAmountMsat, outgoingAmountMsat int64) error {
return nil return nil
} }
func isConnected(ctx context.Context, client lnrpc.LightningClient, destination []byte) error {
pubKey := hex.EncodeToString(destination)
r, err := client.ListPeers(ctx, &lnrpc.ListPeersRequest{LatestError: true})
if err != nil {
log.Printf("client.ListPeers() error: %v", err)
return fmt.Errorf("client.ListPeers() error: %w", err)
}
for _, peer := range r.Peers {
if pubKey == peer.PubKey {
log.Printf("destination online: %x", destination)
return nil
}
}
log.Printf("destination offline: %x", destination)
return fmt.Errorf("destination offline")
}
func openChannel(ctx context.Context, client lnrpc.LightningClient, paymentHash, destination []byte, incomingAmountMsat int64) ([]byte, uint32, error) { func openChannel(ctx context.Context, client lnrpc.LightningClient, paymentHash, destination []byte, incomingAmountMsat int64) ([]byte, uint32, error) {
capacity := incomingAmountMsat/1000 + additionalChannelCapacity capacity := incomingAmountMsat/1000 + additionalChannelCapacity
channelPoint, err := client.OpenChannelSync(ctx, &lnrpc.OpenChannelRequest{ channelPoint, err := client.OpenChannelSync(ctx, &lnrpc.OpenChannelRequest{
@@ -104,21 +122,34 @@ func intercept() {
request.OnionBlob, request.OnionBlob,
) )
paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, fundingTxID, fundingTxOutnum, err := paymentInfo(request.PaymentHash) paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, fundingTxID, fundingTxOutnum, err := paymentInfo(request.PaymentHash)
if err != nil { if err != nil {
log.Printf("paymentInfo(%x) error: %v", request.PaymentHash, err) log.Printf("paymentInfo(%x) error: %v", request.PaymentHash, err)
} }
log.Printf("paymentSecret: %x\ndestination:%x\nincomingAmountMsat:%v\noutgoingAmountMsat:%v\n\n", log.Printf("paymentHash:%x\npaymentSecret:%x\ndestination:%x\nincomingAmountMsat:%v\noutgoingAmountMsat:%v\n\n",
paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat) paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat)
if paymentSecret != nil { if paymentSecret != nil {
if fundingTxID == nil { if fundingTxID == nil {
fundingTxID, fundingTxOutnum, err = openChannel(clientCtx, client, request.PaymentHash, destination, incomingAmountMsat) if bytes.Compare(paymentHash, request.PaymentHash) == 0 {
log.Printf("openChannel(%x, %v) err: %v", destination, incomingAmountMsat, err) fundingTxID, fundingTxOutnum, err = openChannel(clientCtx, client, request.PaymentHash, destination, incomingAmountMsat)
if err != nil { log.Printf("openChannel(%x, %v) err: %v", destination, incomingAmountMsat, err)
if err != nil {
interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{
IncomingCircuitKey: request.IncomingCircuitKey,
Action: routerrpc.ResolveHoldForwardAction_FAIL,
})
continue
}
} else { //probing
failureCode := routerrpc.ForwardHtlcInterceptResponse_TEMPORARY_CHANNEL_FAILURE
if err := isConnected(clientCtx, client, destination); err == nil {
failureCode = routerrpc.ForwardHtlcInterceptResponse_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS
}
interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{
IncomingCircuitKey: request.IncomingCircuitKey, IncomingCircuitKey: request.IncomingCircuitKey,
Action: routerrpc.ResolveHoldForwardAction_FAIL, Action: routerrpc.ResolveHoldForwardAction_FAIL,
FailureCode: failureCode,
}) })
continue continue
} }

View File

@@ -0,0 +1 @@
DROP INDEX probe_payment_hash;

View File

@@ -0,0 +1 @@
CREATE INDEX probe_payment_hash ON public.payments (sha256('probing-01:' || payment_hash));