Files
electronwall/htlcInterceptor.go
2022-12-23 11:51:01 +01:00

298 lines
9.6 KiB
Go

package main
import (
"context"
"encoding/hex"
"encoding/json"
"fmt"
"strings"
"sync"
"github.com/callebtc/electronwall/config"
"github.com/callebtc/electronwall/rules"
"github.com/callebtc/electronwall/types"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
log "github.com/sirupsen/logrus"
)
// getHtlcForwardEvent returns a struct containing relevant information about the current
// forward request that the decision engine can then use
func (app *App) getHtlcForwardEvent(ctx context.Context, event *routerrpc.ForwardHtlcInterceptRequest) (types.HtlcForwardEvent, error) {
channelEdge, err := app.lnd.getPubKeyFromChannel(ctx, event.IncomingCircuitKey.ChanId)
if err != nil {
log.Errorf("[forward] Error getting pubkey for channel %s", ParseChannelID(event.IncomingCircuitKey.ChanId))
}
var pubkeyFrom, aliasFrom, pubkeyTo, aliasTo string
if channelEdge.Node1Pub != app.myInfo.IdentityPubkey {
pubkeyFrom = channelEdge.Node1Pub
} else {
pubkeyFrom = channelEdge.Node2Pub
}
aliasFrom, err = app.lnd.getNodeAlias(ctx, pubkeyFrom)
if err != nil {
aliasFrom = trimPubKey([]byte(pubkeyFrom))
log.Errorf("[forward] Error getting alias for node %s", aliasFrom)
}
// we need to figure out which side of the channel is the other end
channelEdgeTo, err := app.lnd.getPubKeyFromChannel(ctx, event.OutgoingRequestedChanId)
if err != nil {
log.Errorf("[forward] Error getting pubkey for channel %s", ParseChannelID(event.OutgoingRequestedChanId))
return types.HtlcForwardEvent{}, err
}
if channelEdgeTo.Node1Pub != app.myInfo.IdentityPubkey {
pubkeyTo = channelEdgeTo.Node1Pub
} else {
pubkeyTo = channelEdgeTo.Node2Pub
}
aliasTo, err = app.lnd.getNodeAlias(ctx, pubkeyTo)
if err != nil {
aliasTo = trimPubKey([]byte(pubkeyTo))
log.Errorf("[forward] Error getting alias for node %s", aliasTo)
}
return types.HtlcForwardEvent{
PubkeyFrom: pubkeyFrom,
AliasFrom: aliasFrom,
PubkeyTo: pubkeyTo,
AliasTo: aliasTo,
Event: event,
}, nil
}
// DispatchHTLCAcceptor is the HTLC acceptor event loop
func (app *App) DispatchHTLCAcceptor(ctx context.Context) {
go func() {
err := app.logHtlcEvents(ctx)
if err != nil {
log.Error("htlc event logger error",
"err", err)
}
}()
go func() {
err := app.interceptHtlcEvents(ctx)
if err != nil {
log.Error("htlc interceptor error",
"err", err)
}
// release wait group for htlc interceptor
ctx.Value(ctxKeyWaitGroup).(*sync.WaitGroup).Done()
}()
log.Info("[forward] Listening for incoming HTLCs")
}
// interceptHtlcEvents intercepts incoming htlc events
func (app *App) interceptHtlcEvents(ctx context.Context) error {
// interceptor, decide whether to accept or reject
interceptor, err := app.lnd.htlcInterceptor(ctx)
if err != nil {
return err
}
for {
event, err := interceptor.Recv()
if err != nil {
return err
}
go func() {
log.Tracef("[forward] HTLC event (%d->%d)", event.IncomingCircuitKey.ChanId, event.OutgoingRequestedChanId)
htlcForwardEvent, err := app.getHtlcForwardEvent(ctx, event)
if err != nil {
return
}
forward_info_string := fmt.Sprintf(
"from %s to %s (%d sat, chan_id:%s->%s, htlc_id:%d)",
htlcForwardEvent.AliasFrom,
htlcForwardEvent.AliasTo,
event.IncomingAmountMsat/1000,
ParseChannelID(event.IncomingCircuitKey.ChanId),
ParseChannelID(event.OutgoingRequestedChanId),
event.IncomingCircuitKey.HtlcId,
)
contextLogger := log.WithFields(log.Fields{
"event": "forward_request",
"in_alias": htlcForwardEvent.AliasFrom,
"out_alias": htlcForwardEvent.AliasTo,
"amount": event.IncomingAmountMsat / 1000,
"in_chan_id": ParseChannelID(event.IncomingCircuitKey.ChanId),
"out_chan_id": ParseChannelID(event.OutgoingRequestedChanId),
"htlc_id": event.IncomingCircuitKey.HtlcId,
})
// decision for routing
decision_chan := make(chan bool, 1)
list_decision, err := app.htlcInterceptDecision(ctx, event, decision_chan)
if err != nil {
return
}
rules_decision, err := rules.Apply(htlcForwardEvent, decision_chan)
if err != nil {
fmt.Printf("script error: %v", err)
return
}
accept := true
if !list_decision || !rules_decision {
accept = false
}
response := &routerrpc.ForwardHtlcInterceptResponse{
IncomingCircuitKey: event.IncomingCircuitKey,
}
switch accept {
case true:
if config.Configuration.LogJson {
contextLogger.Infof("allow")
} else {
log.Infof("[forward] ✅ Allow HTLC %s", forward_info_string)
}
response.Action = routerrpc.ResolveHoldForwardAction_RESUME
case false:
if config.Configuration.LogJson {
contextLogger.Infof("deny")
} else {
log.Infof("[forward] ❌ Deny HTLC %s", forward_info_string)
}
response.Action = routerrpc.ResolveHoldForwardAction_FAIL
}
err = interceptor.Send(response)
if err != nil {
return
}
}()
}
}
// htlcInterceptDecision implements the rules upon which the
// decision is made whether or not to relay an HTLC to the next
// peer.
// The decision is made based on the following rules:
// 1. Either use a allowlist or a denylist.
// 2. If a single channel ID is used (12320768x65536x0), check the incoming ID of the HTLC against the list.
// 3. If two channel IDs are used (7929856x65537x0->7143424x65537x0), check the incoming ID and the outgoing ID of the HTLC against the list.
func (app *App) htlcInterceptDecision(ctx context.Context, event *routerrpc.ForwardHtlcInterceptRequest, decision_chan chan bool) (bool, error) {
var accept bool
var listToParse []string
// // sleep for 60 seconds
// time.Sleep(60 * time.Second)
// determine filtering mode and list to parse
switch config.Configuration.ForwardMode {
case "allowlist":
accept = false
listToParse = config.Configuration.ForwardAllowlist
case "denylist":
accept = true
listToParse = config.Configuration.ForwardDenylist
default:
return false, fmt.Errorf("unknown forward mode: %s", config.Configuration.ForwardMode)
}
// parse list and decide
for _, forward_list_entry := range listToParse {
if forward_list_entry == "*" {
accept = !accept
break
}
if len(strings.Split(forward_list_entry, "->")) == 2 {
// check if entry is a pair of from->to
split := strings.Split(forward_list_entry, "->")
from_channel_id, to_channel_id := split[0], split[1]
if (ParseChannelID(event.IncomingCircuitKey.ChanId) == from_channel_id || from_channel_id == "*") &&
(ParseChannelID(event.OutgoingRequestedChanId) == to_channel_id || to_channel_id == "*") {
accept = !accept
log.Tracef("[test] Incoming: %s <-> %s, Outgoing: %s <-> %s", ParseChannelID(event.IncomingCircuitKey.ChanId), from_channel_id, ParseChannelID(event.OutgoingRequestedChanId), to_channel_id)
break
}
} else {
// single entry
if ParseChannelID(event.IncomingCircuitKey.ChanId) == forward_list_entry {
accept = !accept
break
}
}
}
// decision_chan <- accept
log.Infof("[list] decision: %t", accept)
return accept, nil
}
// logHtlcEvents reports on incoming htlc events
func (app *App) logHtlcEvents(ctx context.Context) error {
// htlc event subscriber, reports on incoming htlc events
stream, err := app.lnd.subscribeHtlcEvents(ctx, &routerrpc.SubscribeHtlcEventsRequest{})
if err != nil {
return err
}
for {
event, err := stream.Recv()
if err != nil {
return err
}
// we only care about HTLC forward events
if event.EventType != routerrpc.HtlcEvent_FORWARD {
continue
}
contextLogger := func(event *routerrpc.HtlcEvent) *log.Entry {
b, err := json.Marshal(event)
if err != nil {
panic(err)
}
return log.WithFields(log.Fields{
"type": "forward",
"event": string(b),
})
}
switch event.Event.(type) {
case *routerrpc.HtlcEvent_SettleEvent:
if config.Configuration.LogJson {
contextLogger(event).Infof("SettleEvent")
// contextLogger.Debugf("[forward] Preimage: %s", hex.EncodeToString(event.GetSettleEvent().Preimage))
} else {
log.Infof("[forward] ⚡️ HTLC SettleEvent (chan_id:%s, htlc_id:%d)", ParseChannelID(event.IncomingChannelId), event.IncomingHtlcId)
if event.GetSettleEvent() != nil && event.GetSettleEvent().Preimage != nil {
log.Debugf("[forward] Preimage: %s", hex.EncodeToString(event.GetSettleEvent().Preimage))
}
}
case *routerrpc.HtlcEvent_ForwardFailEvent:
if config.Configuration.LogJson {
contextLogger(event).Infof("ForwardFailEvent")
// contextLogger.Debugf("[forward] Reason: %s", event.GetForwardFailEvent())
} else {
log.Infof("[forward] HTLC ForwardFailEvent (chan_id:%s, htlc_id:%d)", ParseChannelID(event.IncomingChannelId), event.IncomingHtlcId)
// log.Debugf("[forward] Reason: %s", event.GetForwardFailEvent().String())
}
case *routerrpc.HtlcEvent_ForwardEvent:
if config.Configuration.LogJson {
contextLogger(event).Infof("ForwardEvent")
} else {
log.Infof("[forward] HTLC ForwardEvent (chan_id:%s, htlc_id:%d)", ParseChannelID(event.IncomingChannelId), event.IncomingHtlcId)
}
// log.Infof("[forward] HTLC ForwardEvent (chan_id:%s, htlc_id:%d)", ParseChannelID(event.IncomingChannelId), event.IncomingHtlcId)
// log.Debugf("[forward] Details: %s", event.GetForwardEvent().String())
case *routerrpc.HtlcEvent_LinkFailEvent:
if config.Configuration.LogJson {
contextLogger(event).Infof("LinkFailEvent")
// contextLogger(event).Debugf("[forward] Reason: %s", event.GetLinkFailEvent().FailureString)
} else {
log.Infof("[forward] HTLC LinkFailEvent (chan_id:%s, htlc_id:%d)", ParseChannelID(event.IncomingChannelId), event.IncomingHtlcId)
log.Debugf("[forward] Reason: %s", event.GetLinkFailEvent().FailureString)
}
}
}
}