mirror of
https://github.com/callebtc/electronwall.git
synced 2025-12-17 07:04:21 +01:00
works
This commit is contained in:
188
README.md
188
README.md
@@ -1,7 +1,9 @@
|
||||
# ⚡️🛡 electronwall
|
||||
A tiny firewall for LND that can filter Lightning **channel opening requests** and **HTLC forwards** on your node based on set rules.
|
||||
A tiny firewall for LND that can filter Lightning **channel opening requests** and **HTLC forwards** based on your custom rules.
|
||||
|
||||
Currently, electronwall uses filter lists that either allow (allowlist) or reject (denylist) events from a list of node public keys for channel openings, or channel IDs and channel pairs for payment routings.
|
||||
electronwall uses filter lists that either allow (allowlist) or reject (denylist) events from a list of node public keys for channel openings, or channel IDs and channel pairs for payment routings.
|
||||
|
||||
You can also write [custom rules](#programmable-rules) using a builtin Javascript engine.
|
||||
|
||||

|
||||
|
||||
@@ -28,3 +30,185 @@ Edit `config.yaml.example` and rename to `config.yaml`.
|
||||
```bash
|
||||
./electronwall
|
||||
```
|
||||
|
||||
---------------
|
||||
# Rules
|
||||
|
||||
## Allowlist and denylist
|
||||
|
||||
Allowlist and denylist rules are set in `config.yaml` under the appropriate keys. See the [example](config.yaml.example) config.
|
||||
|
||||
## Programmable rules
|
||||
|
||||
electronwall has a Javascript engine called [goja](https://github.com/dop251/goja) that allows you to set custom rules. Note that you can only use pure Javascript (ECMAScript), you can't import a ton of other dependcies like with web applications.
|
||||
|
||||
Rules are saved in the `rules/` directory. There are two files, one for channel open requests `ChannelAccept.js` and one for HTLC forwards `HtlcForward.js`.
|
||||
|
||||
electronwall passes [contextual information](#contextual-information) to the Javascript engine that you can use to create rich rules. See below for a list of objects that are currently supported.
|
||||
|
||||
Here is one rather complex rule for channel accept decisions in `ChannelAccept.js` for demonstration purposes:
|
||||
|
||||
```javascript
|
||||
// only channels > 0.75 Msat
|
||||
ChannelAccept.Event.FundingAmt >= 750000 &&
|
||||
// nodes with high 1ML availability score
|
||||
ChannelAccept.OneMl.Noderank.Availability > 100 &&
|
||||
// nodes with a low enough 1ML age rank
|
||||
ChannelAccept.OneMl.Noderank.Age < 10000 &&
|
||||
(
|
||||
// only nodes with Amboss contact data
|
||||
ChannelAccept.Amboss.Socials.Info.Email ||
|
||||
ChannelAccept.Amboss.Socials.Info.Twitter ||
|
||||
ChannelAccept.Amboss.Socials.Info.Telegram
|
||||
) &&
|
||||
(
|
||||
// elitist: either nodes with amboss prime
|
||||
ChannelAccept.Amboss.Amboss.IsPrime ||
|
||||
// or nodes with high-ranking capacity
|
||||
ChannelAccept.Amboss.GraphInfo.Metrics.CapacityRank < 1000 ||
|
||||
// or nodes with high-ranking channel count
|
||||
ChannelAccept.Amboss.GraphInfo.Metrics.ChannelsRank < 1000
|
||||
)
|
||||
```
|
||||
|
||||
Here is an example `HtlcForward.js`
|
||||
```javascript
|
||||
if (
|
||||
// only forward amounts larger than 100 sat
|
||||
HtlcForward.Event.OutgoingAmountMsat >= 100000
|
||||
) { true } else { false }
|
||||
```
|
||||
|
||||
### Contextual information
|
||||
Here is a list of all objects that are passed to the Javascript engine. You need to look at the structure of these objects in order to use them in a custom rule like the example above.
|
||||
|
||||
#### LND: ChannelAcceptRequest `*.Event`
|
||||
```go
|
||||
type ChannelAcceptRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// The pubkey of the node that wishes to open an inbound channel.
|
||||
NodePubkey []byte `protobuf:"bytes,1,opt,name=node_pubkey,json=nodePubkey,proto3" json:"node_pubkey,omitempty"`
|
||||
// The hash of the genesis block that the proposed channel resides in.
|
||||
ChainHash []byte `protobuf:"bytes,2,opt,name=chain_hash,json=chainHash,proto3" json:"chain_hash,omitempty"`
|
||||
// The pending channel id.
|
||||
PendingChanId []byte `protobuf:"bytes,3,opt,name=pending_chan_id,json=pendingChanId,proto3" json:"pending_chan_id,omitempty"`
|
||||
// The funding amount in satoshis that initiator wishes to use in the
|
||||
// channel.
|
||||
FundingAmt uint64 `protobuf:"varint,4,opt,name=funding_amt,json=fundingAmt,proto3" json:"funding_amt,omitempty"`
|
||||
// The push amount of the proposed channel in millisatoshis.
|
||||
PushAmt uint64 `protobuf:"varint,5,opt,name=push_amt,json=pushAmt,proto3" json:"push_amt,omitempty"`
|
||||
// The dust limit of the initiator's commitment tx.
|
||||
DustLimit uint64 `protobuf:"varint,6,opt,name=dust_limit,json=dustLimit,proto3" json:"dust_limit,omitempty"`
|
||||
// The maximum amount of coins in millisatoshis that can be pending in this
|
||||
// channel.
|
||||
MaxValueInFlight uint64 `protobuf:"varint,7,opt,name=max_value_in_flight,json=maxValueInFlight,proto3" json:"max_value_in_flight,omitempty"`
|
||||
// The minimum amount of satoshis the initiator requires us to have at all
|
||||
// times.
|
||||
ChannelReserve uint64 `protobuf:"varint,8,opt,name=channel_reserve,json=channelReserve,proto3" json:"channel_reserve,omitempty"`
|
||||
// The smallest HTLC in millisatoshis that the initiator will accept.
|
||||
MinHtlc uint64 `protobuf:"varint,9,opt,name=min_htlc,json=minHtlc,proto3" json:"min_htlc,omitempty"`
|
||||
// The initial fee rate that the initiator suggests for both commitment
|
||||
// transactions.
|
||||
FeePerKw uint64 `protobuf:"varint,10,opt,name=fee_per_kw,json=feePerKw,proto3" json:"fee_per_kw,omitempty"`
|
||||
//
|
||||
//The number of blocks to use for the relative time lock in the pay-to-self
|
||||
//output of both commitment transactions.
|
||||
CsvDelay uint32 `protobuf:"varint,11,opt,name=csv_delay,json=csvDelay,proto3" json:"csv_delay,omitempty"`
|
||||
// The total number of incoming HTLC's that the initiator will accept.
|
||||
MaxAcceptedHtlcs uint32 `protobuf:"varint,12,opt,name=max_accepted_htlcs,json=maxAcceptedHtlcs,proto3" json:"max_accepted_htlcs,omitempty"`
|
||||
// A bit-field which the initiator uses to specify proposed channel
|
||||
// behavior.
|
||||
ChannelFlags uint32 `protobuf:"varint,13,opt,name=channel_flags,json=channelFlags,proto3" json:"channel_flags,omitempty"`
|
||||
// The commitment type the initiator wishes to use for the proposed channel.
|
||||
CommitmentType CommitmentType `protobuf:"varint,14,opt,name=commitment_type,json=commitmentType,proto3,enum=lnrpc.CommitmentType" json:"commitment_type,omitempty"`
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 1ML node information `*.OneMl`
|
||||
```go
|
||||
type OneML_NodeInfoResponse struct {
|
||||
LastUpdate int `json:"last_update"`
|
||||
PubKey string `json:"pub_key"`
|
||||
Alias string `json:"alias"`
|
||||
Addresses []struct {
|
||||
Network string `json:"network"`
|
||||
Addr string `json:"addr"`
|
||||
} `json:"addresses"`
|
||||
Color string `json:"color"`
|
||||
Capacity int `json:"capacity"`
|
||||
Channelcount int `json:"channelcount"`
|
||||
Noderank struct {
|
||||
Capacity int `json:"capacity"`
|
||||
Channelcount int `json:"channelcount"`
|
||||
Age int `json:"age"`
|
||||
Growth int `json:"growth"`
|
||||
Availability int `json:"availability"`
|
||||
} `json:"noderank"`
|
||||
}
|
||||
```
|
||||
#### Amboss node information `*.Amboss`
|
||||
```go
|
||||
type Amboss_NodeInfoResponse struct {
|
||||
Socials struct {
|
||||
Info struct {
|
||||
Email string `json:"email"`
|
||||
Telegram string `json:"telegram"`
|
||||
Twitter string `json:"twitter"`
|
||||
LightningAddress string `json:"lightning_address"`
|
||||
Website string `json:"website"`
|
||||
Pubkey string `json:"pubkey"`
|
||||
MinChannelSize interface{} `json:"minChannelSize"`
|
||||
Message string `json:"message"`
|
||||
TwitterVerified bool `json:"twitter_verified"`
|
||||
Updated time.Time `json:"updated"`
|
||||
} `json:"info"`
|
||||
} `json:"socials"`
|
||||
GraphInfo struct {
|
||||
LastUpdate time.Time `json:"last_update"`
|
||||
Metrics struct {
|
||||
Capacity string `json:"capacity"`
|
||||
CapacityRank int `json:"capacity_rank"`
|
||||
Channels int `json:"channels"`
|
||||
ChannelsRank int `json:"channels_rank"`
|
||||
} `json:"metrics"`
|
||||
Node struct {
|
||||
Addresses []struct {
|
||||
Addr string `json:"addr"`
|
||||
IPInfo struct {
|
||||
City string `json:"city"`
|
||||
Country string `json:"country"`
|
||||
CountryCode string `json:"country_code"`
|
||||
} `json:"ip_info"`
|
||||
Network string `json:"network"`
|
||||
} `json:"addresses"`
|
||||
LastUpdate int `json:"last_update"`
|
||||
Color string `json:"color"`
|
||||
Features []struct {
|
||||
FeatureID string `json:"feature_id"`
|
||||
IsKnown bool `json:"is_known"`
|
||||
IsRequired bool `json:"is_required"`
|
||||
Name string `json:"name"`
|
||||
} `json:"features"`
|
||||
} `json:"node"`
|
||||
} `json:"graph_info"`
|
||||
Amboss struct {
|
||||
IsFavorite bool `json:"is_favorite"`
|
||||
IsPrime bool `json:"is_prime"`
|
||||
NumberFavorites int `json:"number_favorites"`
|
||||
NewChannelGossipDelta struct {
|
||||
Mean string `json:"mean"`
|
||||
Sd string `json:"sd"`
|
||||
} `json:"new_channel_gossip_delta"`
|
||||
Notifications struct {
|
||||
NumberSubscribers int `json:"number_subscribers"`
|
||||
} `json:"notifications"`
|
||||
} `json:"amboss"`
|
||||
}
|
||||
```
|
||||
|
||||
#### Network information `*.Network`
|
||||
*TBD*
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/callebtc/electronwall/config"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -44,7 +45,7 @@ func (c *OneMlClient) GetNodeInfo(pubkey string) (OneML_NodeInfoResponse, error)
|
||||
log.Infof("Getting info from 1ml.com for %s", pubkey)
|
||||
|
||||
client := http.Client{
|
||||
Timeout: time.Second * 2, // Timeout after 2 seconds
|
||||
Timeout: time.Second * time.Duration(config.Configuration.ApiRules.OneMl.Timeout),
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
|
||||
@@ -4,32 +4,11 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/callebtc/electronwall/config"
|
||||
"github.com/machinebox/graphql"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type AutoGenerated struct {
|
||||
Data struct {
|
||||
GetNode struct {
|
||||
GraphInfo struct {
|
||||
Node struct {
|
||||
Alias string `json:"alias"`
|
||||
} `json:"node"`
|
||||
} `json:"graph_info"`
|
||||
} `json:"getNode"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
var simple_query = `query Node($pubkey: String!) {
|
||||
getNode(pubkey: $pubkey) {
|
||||
graph_info {
|
||||
node {
|
||||
alias
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
type Amboss_NodeInfoResponse struct {
|
||||
Socials struct {
|
||||
Info struct {
|
||||
@@ -178,7 +157,10 @@ func (c *AmbossClient) GetNodeInfo(pubkey string) (Amboss_NodeInfoResponse, erro
|
||||
graphqlRequest.Header.Set("Content-Type", "application/json")
|
||||
|
||||
var r_nested Amboss_NodeInfoResponse_Nested
|
||||
if err := graphqlClient.Run(context.Background(), graphqlRequest, &r_nested.Data); err != nil {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(config.Configuration.ApiRules.Amboss.Timeout))
|
||||
defer cancel()
|
||||
|
||||
if err := graphqlClient.Run(ctx, graphqlRequest, &r_nested.Data); err != nil {
|
||||
log.Errorf("[amboss] api error: %v", err)
|
||||
}
|
||||
|
||||
|
||||
45
api/api.go
45
api/api.go
@@ -1,6 +1,9 @@
|
||||
package api
|
||||
|
||||
import log "github.com/sirupsen/logrus"
|
||||
import (
|
||||
"github.com/callebtc/electronwall/config"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type ApiClient interface {
|
||||
GetNodeInfo(pubkey string) OneML_NodeInfoResponse
|
||||
@@ -12,23 +15,31 @@ type ApiNodeInfo struct {
|
||||
}
|
||||
|
||||
func GetApiNodeinfo(pubkey string) (ApiNodeInfo, error) {
|
||||
// get info from 1ml
|
||||
OnemlClient := GetOneMlClient()
|
||||
onemlNodeInfo, err := OnemlClient.GetNodeInfo(pubkey)
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
onemlNodeInfo = OneML_NodeInfoResponse{}
|
||||
response := ApiNodeInfo{
|
||||
OneMl: OneML_NodeInfoResponse{},
|
||||
Amboss: Amboss_NodeInfoResponse{},
|
||||
}
|
||||
|
||||
// get info from amboss
|
||||
ambossClient := GetAmbossClient()
|
||||
ambossNodeInfo, err := ambossClient.GetNodeInfo(pubkey)
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
ambossNodeInfo = Amboss_NodeInfoResponse{}
|
||||
if config.Configuration.ApiRules.OneMl.Active {
|
||||
// get info from 1ml
|
||||
OnemlClient := GetOneMlClient()
|
||||
onemlNodeInfo, err := OnemlClient.GetNodeInfo(pubkey)
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
onemlNodeInfo = OneML_NodeInfoResponse{}
|
||||
}
|
||||
response.OneMl = onemlNodeInfo
|
||||
}
|
||||
return ApiNodeInfo{
|
||||
OneMl: onemlNodeInfo,
|
||||
Amboss: ambossNodeInfo,
|
||||
}, err
|
||||
|
||||
if config.Configuration.ApiRules.Amboss.Active {
|
||||
// get info from amboss
|
||||
ambossClient := GetAmbossClient()
|
||||
ambossNodeInfo, err := ambossClient.GetNodeInfo(pubkey)
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
ambossNodeInfo = Amboss_NodeInfoResponse{}
|
||||
}
|
||||
response.Amboss = ambossNodeInfo
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/callebtc/electronwall/api"
|
||||
"github.com/callebtc/electronwall/config"
|
||||
"github.com/callebtc/electronwall/rules"
|
||||
"github.com/callebtc/electronwall/types"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
@@ -25,7 +26,7 @@ func (app *App) GetChannelAcceptEvent(ctx context.Context, req lnrpc.ChannelAcce
|
||||
log.Errorf(err.Error())
|
||||
}
|
||||
|
||||
noeInfo, err := api.GetApiNodeinfo(string(req.NodePubkey))
|
||||
noeInfo, err := api.GetApiNodeinfo(hex.EncodeToString(req.NodePubkey))
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
}
|
||||
@@ -125,7 +126,7 @@ func (app *App) interceptChannelEvents(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
// parse list
|
||||
list_decision, err := app.channelAcceptDecision(req)
|
||||
list_decision, err := app.channelAcceptListDecision(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -137,7 +138,7 @@ func (app *App) interceptChannelEvents(ctx context.Context) error {
|
||||
|
||||
res := lnrpc.ChannelAcceptResponse{}
|
||||
if accept {
|
||||
if Configuration.LogJson {
|
||||
if config.Configuration.LogJson {
|
||||
contextLogger.Infof("allow")
|
||||
} else {
|
||||
log.Infof("[channel] ✅ Allow channel %s", channel_info_string)
|
||||
@@ -152,13 +153,13 @@ func (app *App) interceptChannelEvents(ctx context.Context) error {
|
||||
}
|
||||
|
||||
} else {
|
||||
if Configuration.LogJson {
|
||||
if config.Configuration.LogJson {
|
||||
contextLogger.Infof("deny")
|
||||
} else {
|
||||
log.Infof("[channel] ❌ Deny channel %s", channel_info_string)
|
||||
}
|
||||
res = lnrpc.ChannelAcceptResponse{Accept: false,
|
||||
Error: Configuration.ChannelRejectMessage}
|
||||
Error: config.Configuration.ChannelRejectMessage}
|
||||
}
|
||||
err = acceptClient.Send(&res)
|
||||
if err != nil {
|
||||
@@ -168,26 +169,26 @@ func (app *App) interceptChannelEvents(ctx context.Context) error {
|
||||
|
||||
}
|
||||
|
||||
func (app *App) channelAcceptDecision(req lnrpc.ChannelAcceptRequest) (bool, error) {
|
||||
func (app *App) channelAcceptListDecision(req lnrpc.ChannelAcceptRequest) (bool, error) {
|
||||
// determine mode and list of channels to parse
|
||||
var accept bool
|
||||
var listToParse []string
|
||||
if Configuration.ChannelMode == "allowlist" {
|
||||
if config.Configuration.ChannelMode == "allowlist" {
|
||||
accept = false
|
||||
listToParse = Configuration.ChannelAllowlist
|
||||
} else if Configuration.ChannelMode == "denylist" {
|
||||
listToParse = config.Configuration.ChannelAllowlist
|
||||
} else if config.Configuration.ChannelMode == "denylist" {
|
||||
accept = true
|
||||
listToParse = Configuration.ChannelDenylist
|
||||
listToParse = config.Configuration.ChannelDenylist
|
||||
}
|
||||
|
||||
// parse and make decision
|
||||
log.Infof("TRYING %s", string(req.NodePubkey))
|
||||
for _, pubkey := range listToParse {
|
||||
if string(req.NodePubkey) == pubkey || pubkey == "*" {
|
||||
if hex.EncodeToString(req.NodePubkey) == pubkey || pubkey == "*" {
|
||||
accept = !accept
|
||||
break
|
||||
}
|
||||
}
|
||||
log.Infof("[list] decision: %t", accept)
|
||||
return accept, nil
|
||||
|
||||
}
|
||||
@@ -214,7 +215,7 @@ func (app *App) logChannelEvents(ctx context.Context) error {
|
||||
alias,
|
||||
)
|
||||
|
||||
if Configuration.LogJson {
|
||||
if config.Configuration.LogJson {
|
||||
contextLogger := log.WithFields(log.Fields{
|
||||
"event": "channel",
|
||||
"capacity": event.GetOpenChannel().Capacity,
|
||||
|
||||
@@ -41,3 +41,13 @@ forward-allowlist:
|
||||
- "*->25328x256x0" # all forwards to this channel
|
||||
forward-denylist:
|
||||
- "9961472x65537x1"
|
||||
|
||||
# ---- Javascript rules ----
|
||||
rules:
|
||||
apply: true # whether to respect the rule decision
|
||||
oneml: # 1ML.com API
|
||||
active: true
|
||||
timeout: 5 # API timeout in seconds
|
||||
amboss: # Amboss.space API
|
||||
active: true
|
||||
timeout: 5 # API timeout in seconds
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -20,6 +20,17 @@ var Configuration = struct {
|
||||
ForwardMode string `yaml:"forward-mode"`
|
||||
ForwardAllowlist []string `yaml:"forward-allowlist"`
|
||||
ForwardDenylist []string `yaml:"forward-denylist"`
|
||||
ApiRules struct {
|
||||
Apply bool `yaml:"apply"`
|
||||
OneMl struct {
|
||||
Active bool `yaml:"active"`
|
||||
Timeout int `yaml:"timeout"`
|
||||
} `yaml:"oneml"`
|
||||
Amboss struct {
|
||||
Active bool `yaml:"active"`
|
||||
Timeout int `yaml:"timeout"`
|
||||
} `yaml:"amboss"`
|
||||
} `yaml:"rules"`
|
||||
}{}
|
||||
|
||||
func init() {
|
||||
@@ -31,8 +42,6 @@ func init() {
|
||||
}
|
||||
|
||||
func checkConfig() {
|
||||
setLogger(Configuration.Debug, Configuration.LogJson)
|
||||
welcome()
|
||||
|
||||
if Configuration.Host == "" {
|
||||
panic(fmt.Errorf("no host specified in config.yaml"))
|
||||
@@ -20,12 +20,12 @@ func trimPubKey(pubkey []byte) string {
|
||||
}
|
||||
}
|
||||
|
||||
func welcome() {
|
||||
log.Info("---- ⚡️ electronwall 0.3.3 ⚡️ ----")
|
||||
func Welcome() {
|
||||
log.Info("---- ⚡️ electronwall 0.4 ⚡️ ----")
|
||||
}
|
||||
|
||||
// setLogger will initialize the log format
|
||||
func setLogger(debug bool, json bool) {
|
||||
// SetLogger will initialize the log format
|
||||
func SetLogger(debug bool, json bool) {
|
||||
if json {
|
||||
customFormatter := new(log.JSONFormatter)
|
||||
customFormatter.TimestampFormat = "2006-01-02 15:04:05"
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/callebtc/electronwall/config"
|
||||
"github.com/callebtc/electronwall/rules"
|
||||
"github.com/callebtc/electronwall/types"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||
@@ -144,14 +145,14 @@ func (app *App) interceptHtlcEvents(ctx context.Context) error {
|
||||
}
|
||||
switch accept {
|
||||
case true:
|
||||
if Configuration.LogJson {
|
||||
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 Configuration.LogJson {
|
||||
if config.Configuration.LogJson {
|
||||
contextLogger.Infof("deny")
|
||||
} else {
|
||||
log.Infof("[forward] ❌ Deny HTLC %s", forward_info_string)
|
||||
@@ -181,15 +182,15 @@ func (app *App) htlcInterceptDecision(ctx context.Context, event *routerrpc.Forw
|
||||
// time.Sleep(60 * time.Second)
|
||||
|
||||
// determine filtering mode and list to parse
|
||||
switch Configuration.ForwardMode {
|
||||
switch config.Configuration.ForwardMode {
|
||||
case "allowlist":
|
||||
accept = false
|
||||
listToParse = Configuration.ForwardAllowlist
|
||||
listToParse = config.Configuration.ForwardAllowlist
|
||||
case "denylist":
|
||||
accept = true
|
||||
listToParse = Configuration.ForwardDenylist
|
||||
listToParse = config.Configuration.ForwardDenylist
|
||||
default:
|
||||
return false, fmt.Errorf("unknown forward mode: %s", Configuration.ForwardMode)
|
||||
return false, fmt.Errorf("unknown forward mode: %s", config.Configuration.ForwardMode)
|
||||
}
|
||||
|
||||
// parse list and decide
|
||||
@@ -217,6 +218,7 @@ func (app *App) htlcInterceptDecision(ctx context.Context, event *routerrpc.Forw
|
||||
}
|
||||
}
|
||||
// decision_chan <- accept
|
||||
log.Infof("[list] decision: %t", accept)
|
||||
return accept, nil
|
||||
}
|
||||
|
||||
@@ -251,7 +253,7 @@ func (app *App) logHtlcEvents(ctx context.Context) error {
|
||||
|
||||
switch event.Event.(type) {
|
||||
case *routerrpc.HtlcEvent_SettleEvent:
|
||||
if Configuration.LogJson {
|
||||
if config.Configuration.LogJson {
|
||||
contextLogger(event).Infof("SettleEvent")
|
||||
// contextLogger.Debugf("[forward] Preimage: %s", hex.EncodeToString(event.GetSettleEvent().Preimage))
|
||||
} else {
|
||||
@@ -262,7 +264,7 @@ func (app *App) logHtlcEvents(ctx context.Context) error {
|
||||
}
|
||||
|
||||
case *routerrpc.HtlcEvent_ForwardFailEvent:
|
||||
if Configuration.LogJson {
|
||||
if config.Configuration.LogJson {
|
||||
contextLogger(event).Infof("ForwardFailEvent")
|
||||
// contextLogger.Debugf("[forward] Reason: %s", event.GetForwardFailEvent())
|
||||
} else {
|
||||
@@ -271,7 +273,7 @@ func (app *App) logHtlcEvents(ctx context.Context) error {
|
||||
}
|
||||
|
||||
case *routerrpc.HtlcEvent_ForwardEvent:
|
||||
if Configuration.LogJson {
|
||||
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)
|
||||
@@ -280,7 +282,7 @@ func (app *App) logHtlcEvents(ctx context.Context) error {
|
||||
// log.Debugf("[forward] Details: %s", event.GetForwardEvent().String())
|
||||
|
||||
case *routerrpc.HtlcEvent_LinkFailEvent:
|
||||
if Configuration.LogJson {
|
||||
if config.Configuration.LogJson {
|
||||
contextLogger(event).Infof("LinkFailEvent")
|
||||
// contextLogger(event).Debugf("[forward] Reason: %s", event.GetLinkFailEvent().FailureString)
|
||||
} else {
|
||||
|
||||
11
main.go
11
main.go
@@ -5,6 +5,7 @@ import (
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
|
||||
"github.com/callebtc/electronwall/config"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||
"github.com/lightningnetwork/lnd/macaroons"
|
||||
@@ -39,11 +40,11 @@ func NewApp(ctx context.Context, lnd lndclient) *App {
|
||||
|
||||
// gets the lnd grpc connection
|
||||
func getClientConnection(ctx context.Context) (*grpc.ClientConn, error) {
|
||||
creds, err := credentials.NewClientTLSFromFile(Configuration.TLSPath, "")
|
||||
creds, err := credentials.NewClientTLSFromFile(config.Configuration.TLSPath, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
macBytes, err := ioutil.ReadFile(Configuration.MacaroonPath)
|
||||
macBytes, err := ioutil.ReadFile(config.Configuration.MacaroonPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -60,8 +61,8 @@ func getClientConnection(ctx context.Context) (*grpc.ClientConn, error) {
|
||||
grpc.WithBlock(),
|
||||
grpc.WithPerRPCCredentials(cred),
|
||||
}
|
||||
log.Infof("Connecting to %s", Configuration.Host)
|
||||
conn, err := grpc.DialContext(ctx, Configuration.Host, opts...)
|
||||
log.Infof("Connecting to %s", config.Configuration.Host)
|
||||
conn, err := grpc.DialContext(ctx, config.Configuration.Host, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -84,6 +85,8 @@ func newLndClient(ctx context.Context) (*LndClient, error) {
|
||||
}
|
||||
|
||||
func main() {
|
||||
SetLogger(config.Configuration.Debug, config.Configuration.LogJson)
|
||||
Welcome()
|
||||
ctx := context.Background()
|
||||
for {
|
||||
lnd, err := newLndClient(ctx)
|
||||
|
||||
92
main_test.go
92
main_test.go
@@ -2,12 +2,12 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/callebtc/electronwall/config"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestApp(t *testing.T) {
|
||||
@@ -33,8 +33,8 @@ func TestHTLCDenylist_BothMatch(t *testing.T) {
|
||||
|
||||
app := NewApp(ctx, client)
|
||||
|
||||
Configuration.ForwardMode = "denylist"
|
||||
Configuration.ForwardDenylist = []string{"700762x1327x1->690757x1005x1"}
|
||||
config.Configuration.ForwardMode = "denylist"
|
||||
config.Configuration.ForwardDenylist = []string{"700762x1327x1->690757x1005x1"}
|
||||
|
||||
app.DispatchHTLCAcceptor(ctx)
|
||||
|
||||
@@ -67,8 +67,8 @@ func TestHTLCDenylist_BothNoMatch(t *testing.T) {
|
||||
|
||||
app := NewApp(ctx, client)
|
||||
|
||||
Configuration.ForwardMode = "denylist"
|
||||
Configuration.ForwardDenylist = []string{"700762x1327x1->690757x1005x1"}
|
||||
config.Configuration.ForwardMode = "denylist"
|
||||
config.Configuration.ForwardDenylist = []string{"700762x1327x1->690757x1005x1"}
|
||||
|
||||
app.DispatchHTLCAcceptor(ctx)
|
||||
|
||||
@@ -101,12 +101,12 @@ func TestHTLCDenylist_WildCardOutBothMatch(t *testing.T) {
|
||||
|
||||
app := NewApp(ctx, client)
|
||||
|
||||
Configuration.ForwardMode = "denylist"
|
||||
Configuration.ForwardDenylist = []string{"700762x1327x1->690757x1005x1"}
|
||||
config.Configuration.ForwardMode = "denylist"
|
||||
config.Configuration.ForwardDenylist = []string{"700762x1327x1->690757x1005x1"}
|
||||
|
||||
app.DispatchHTLCAcceptor(ctx)
|
||||
|
||||
Configuration.ForwardDenylist = []string{"700762x1327x1->*"}
|
||||
config.Configuration.ForwardDenylist = []string{"700762x1327x1->*"}
|
||||
|
||||
key := &routerrpc.CircuitKey{
|
||||
ChanId: 770495967390531585,
|
||||
@@ -137,14 +137,14 @@ func TestHTLCDenylist_WildCardOutNoMatch(t *testing.T) {
|
||||
|
||||
app := NewApp(ctx, client)
|
||||
|
||||
Configuration.ForwardMode = "denylist"
|
||||
Configuration.ForwardDenylist = []string{"700762x1327x1->690757x1005x1"}
|
||||
config.Configuration.ForwardMode = "denylist"
|
||||
config.Configuration.ForwardDenylist = []string{"700762x1327x1->690757x1005x1"}
|
||||
|
||||
app.DispatchHTLCAcceptor(ctx)
|
||||
|
||||
// wildcard out, first key doesn't match: should be allowed
|
||||
|
||||
Configuration.ForwardDenylist = []string{"700762x1327x1->*"}
|
||||
config.Configuration.ForwardDenylist = []string{"700762x1327x1->*"}
|
||||
|
||||
key := &routerrpc.CircuitKey{
|
||||
ChanId: 759495353533530113,
|
||||
@@ -176,11 +176,11 @@ func TestHTLCAllowlist_BothMatch(t *testing.T) {
|
||||
|
||||
app.DispatchHTLCAcceptor(ctx)
|
||||
|
||||
Configuration.ForwardMode = "allowlist"
|
||||
config.Configuration.ForwardMode = "allowlist"
|
||||
|
||||
// both keys correct: should be allowed
|
||||
Configuration.ForwardAllowlist = []string{"700762x1327x1->690757x1005x1"}
|
||||
log.Tracef("[test] Mode: %s, Rules: %v", Configuration.ForwardMode, Configuration.ForwardAllowlist)
|
||||
config.Configuration.ForwardAllowlist = []string{"700762x1327x1->690757x1005x1"}
|
||||
log.Tracef("[test] Mode: %s, Rules: %v", config.Configuration.ForwardMode, config.Configuration.ForwardAllowlist)
|
||||
|
||||
key := &routerrpc.CircuitKey{
|
||||
ChanId: 770495967390531585,
|
||||
@@ -212,10 +212,10 @@ func TestHTLCAllowlist_BothNonMatch(t *testing.T) {
|
||||
|
||||
app.DispatchHTLCAcceptor(ctx)
|
||||
|
||||
Configuration.ForwardMode = "allowlist"
|
||||
config.Configuration.ForwardMode = "allowlist"
|
||||
// both keys wrong: should be denied
|
||||
Configuration.ForwardAllowlist = []string{"700762x1327x1->690757x1005x1"}
|
||||
log.Tracef("[test] Mode: %s, Rules: %v", Configuration.ForwardMode, Configuration.ForwardAllowlist)
|
||||
config.Configuration.ForwardAllowlist = []string{"700762x1327x1->690757x1005x1"}
|
||||
log.Tracef("[test] Mode: %s, Rules: %v", config.Configuration.ForwardMode, config.Configuration.ForwardAllowlist)
|
||||
|
||||
key := &routerrpc.CircuitKey{
|
||||
ChanId: 123456789876543210,
|
||||
@@ -246,10 +246,10 @@ func TestHTLCAllowlist_Wildcard(t *testing.T) {
|
||||
|
||||
app.DispatchHTLCAcceptor(ctx)
|
||||
|
||||
Configuration.ForwardMode = "allowlist"
|
||||
config.Configuration.ForwardMode = "allowlist"
|
||||
// wildcard: should be allowed
|
||||
Configuration.ForwardAllowlist = []string{"*"}
|
||||
log.Tracef("[test] Mode: %s, Rules: %v", Configuration.ForwardMode, Configuration.ForwardAllowlist)
|
||||
config.Configuration.ForwardAllowlist = []string{"*"}
|
||||
log.Tracef("[test] Mode: %s, Rules: %v", config.Configuration.ForwardMode, config.Configuration.ForwardAllowlist)
|
||||
key := &routerrpc.CircuitKey{
|
||||
ChanId: 123456789876543210,
|
||||
HtlcId: 1337000,
|
||||
@@ -279,11 +279,11 @@ func TestHTLCAllowlist_WildcardIn(t *testing.T) {
|
||||
|
||||
app.DispatchHTLCAcceptor(ctx)
|
||||
|
||||
Configuration.ForwardMode = "allowlist"
|
||||
config.Configuration.ForwardMode = "allowlist"
|
||||
// wildcard in: should be allowed
|
||||
|
||||
Configuration.ForwardAllowlist = []string{"*->690757x1005x1"}
|
||||
log.Tracef("[test] Mode: %s, Rules: %v", Configuration.ForwardMode, Configuration.ForwardAllowlist)
|
||||
config.Configuration.ForwardAllowlist = []string{"*->690757x1005x1"}
|
||||
log.Tracef("[test] Mode: %s, Rules: %v", config.Configuration.ForwardMode, config.Configuration.ForwardAllowlist)
|
||||
|
||||
key := &routerrpc.CircuitKey{
|
||||
ChanId: 123456789876543210,
|
||||
@@ -314,10 +314,10 @@ func TestHTLCAllowlist_WildcardOut(t *testing.T) {
|
||||
|
||||
app.DispatchHTLCAcceptor(ctx)
|
||||
|
||||
Configuration.ForwardMode = "allowlist"
|
||||
config.Configuration.ForwardMode = "allowlist"
|
||||
// wildcard out: should be allowed
|
||||
Configuration.ForwardAllowlist = []string{"700762x1327x1->*"}
|
||||
log.Tracef("[test] Mode: %s, Rules: %v", Configuration.ForwardMode, Configuration.ForwardAllowlist)
|
||||
config.Configuration.ForwardAllowlist = []string{"700762x1327x1->*"}
|
||||
log.Tracef("[test] Mode: %s, Rules: %v", config.Configuration.ForwardMode, config.Configuration.ForwardAllowlist)
|
||||
|
||||
key := &routerrpc.CircuitKey{
|
||||
ChanId: 770495967390531585,
|
||||
@@ -348,10 +348,10 @@ func TestHTLCAllowlist_WildcardOutWrongKeyIn(t *testing.T) {
|
||||
|
||||
app.DispatchHTLCAcceptor(ctx)
|
||||
|
||||
Configuration.ForwardMode = "allowlist"
|
||||
config.Configuration.ForwardMode = "allowlist"
|
||||
// wildcard out but wrong in key: should be denied
|
||||
Configuration.ForwardAllowlist = []string{"700762x1327x1->*"}
|
||||
log.Tracef("[test] Mode: %s, Rules: %v", Configuration.ForwardMode, Configuration.ForwardAllowlist)
|
||||
config.Configuration.ForwardAllowlist = []string{"700762x1327x1->*"}
|
||||
log.Tracef("[test] Mode: %s, Rules: %v", config.Configuration.ForwardMode, config.Configuration.ForwardAllowlist)
|
||||
|
||||
key := &routerrpc.CircuitKey{
|
||||
ChanId: 123456789876543210,
|
||||
@@ -382,10 +382,10 @@ func TestHTLCAllowlist_WildcardIn_WrongKeyOut(t *testing.T) {
|
||||
|
||||
app.DispatchHTLCAcceptor(ctx)
|
||||
|
||||
Configuration.ForwardMode = "allowlist"
|
||||
config.Configuration.ForwardMode = "allowlist"
|
||||
// wildcard in but wrong out key: should be denied
|
||||
Configuration.ForwardAllowlist = []string{"*->700762x1327x1"}
|
||||
log.Tracef("[test] Mode: %s, Rules: %v", Configuration.ForwardMode, Configuration.ForwardAllowlist)
|
||||
config.Configuration.ForwardAllowlist = []string{"*->700762x1327x1"}
|
||||
log.Tracef("[test] Mode: %s, Rules: %v", config.Configuration.ForwardMode, config.Configuration.ForwardAllowlist)
|
||||
|
||||
key := &routerrpc.CircuitKey{
|
||||
ChanId: 123456789876543210,
|
||||
@@ -416,10 +416,10 @@ func TestHTLCAllowlist_WildcardBoth(t *testing.T) {
|
||||
|
||||
app.DispatchHTLCAcceptor(ctx)
|
||||
|
||||
Configuration.ForwardMode = "allowlist"
|
||||
config.Configuration.ForwardMode = "allowlist"
|
||||
// wildcard both: should be allowed
|
||||
Configuration.ForwardAllowlist = []string{"*->*"}
|
||||
log.Tracef("[test] Mode: %s, Rules: %v", Configuration.ForwardMode, Configuration.ForwardAllowlist)
|
||||
config.Configuration.ForwardAllowlist = []string{"*->*"}
|
||||
log.Tracef("[test] Mode: %s, Rules: %v", config.Configuration.ForwardMode, config.Configuration.ForwardAllowlist)
|
||||
|
||||
key := &routerrpc.CircuitKey{
|
||||
ChanId: 123456789876543210,
|
||||
@@ -452,8 +452,8 @@ func TestChannelAllowlist_CorrectKey(t *testing.T) {
|
||||
|
||||
app := NewApp(ctx, client)
|
||||
|
||||
Configuration.ChannelMode = "allowlist"
|
||||
Configuration.ChannelAllowlist = []string{"03006fcf3312dae8d068ea297f58e2bd00ec1ffe214b793eda46966b6294a53ce6"}
|
||||
config.Configuration.ChannelMode = "allowlist"
|
||||
config.Configuration.ChannelAllowlist = []string{"03006fcf3312dae8d068ea297f58e2bd00ec1ffe214b793eda46966b6294a53ce6"}
|
||||
|
||||
app.DispatchChannelAcceptor(ctx)
|
||||
|
||||
@@ -476,8 +476,8 @@ func TestChannelAllowlist_WrongKey(t *testing.T) {
|
||||
|
||||
app := NewApp(ctx, client)
|
||||
|
||||
Configuration.ChannelMode = "allowlist"
|
||||
Configuration.ChannelAllowlist = []string{"03006fcf3312dae8d068ea297f58e2bd00ec1ffe214b793eda46966b6294a53ce6"}
|
||||
config.Configuration.ChannelMode = "allowlist"
|
||||
config.Configuration.ChannelAllowlist = []string{"03006fcf3312dae8d068ea297f58e2bd00ec1ffe214b793eda46966b6294a53ce6"}
|
||||
|
||||
app.DispatchChannelAcceptor(ctx)
|
||||
// wrong key: should be denied
|
||||
@@ -501,8 +501,8 @@ func TestChannelAllowlist_Wildcard(t *testing.T) {
|
||||
app.DispatchChannelAcceptor(ctx)
|
||||
|
||||
// wildcard: should be allowed
|
||||
Configuration.ChannelMode = "allowlist"
|
||||
Configuration.ChannelAllowlist = []string{"*"}
|
||||
config.Configuration.ChannelMode = "allowlist"
|
||||
config.Configuration.ChannelAllowlist = []string{"*"}
|
||||
|
||||
client.channelAcceptorRequests <- &lnrpc.ChannelAcceptRequest{
|
||||
NodePubkey: []byte("03006fcf3312dae8d068ea297f58e2bd00ec1ffe214b793eda46966b6294a53ce6"),
|
||||
@@ -521,8 +521,8 @@ func TestChannelDenylist_Match(t *testing.T) {
|
||||
|
||||
app := NewApp(ctx, client)
|
||||
|
||||
Configuration.ChannelMode = "denylist"
|
||||
Configuration.ChannelDenylist = []string{"03006fcf3312dae8d068ea297f58e2bd00ec1ffe214b793eda46966b6294a53ce6"}
|
||||
config.Configuration.ChannelMode = "denylist"
|
||||
config.Configuration.ChannelDenylist = []string{"03006fcf3312dae8d068ea297f58e2bd00ec1ffe214b793eda46966b6294a53ce6"}
|
||||
|
||||
app.DispatchChannelAcceptor(ctx)
|
||||
|
||||
@@ -545,8 +545,8 @@ func TestChannelAllowlist_Match(t *testing.T) {
|
||||
|
||||
app := NewApp(ctx, client)
|
||||
|
||||
Configuration.ChannelMode = "allowlist"
|
||||
Configuration.ChannelAllowlist = []string{"03006fcf3312dae8d068ea297f58e2bd00ec1ffe214b793eda46966b6294a53ce6"}
|
||||
config.Configuration.ChannelMode = "allowlist"
|
||||
config.Configuration.ChannelAllowlist = []string{"03006fcf3312dae8d068ea297f58e2bd00ec1ffe214b793eda46966b6294a53ce6"}
|
||||
|
||||
app.DispatchChannelAcceptor(ctx)
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
if (
|
||||
// only forward amounts larger than 100 sat
|
||||
HtlcForward.Event.OutgoingAmountMsat >= 100000
|
||||
) { true } else { false }
|
||||
|
||||
|
||||
@@ -4,12 +4,18 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/callebtc/electronwall/config"
|
||||
"github.com/callebtc/electronwall/types"
|
||||
"github.com/dop251/goja"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func Apply(s interface{}, decision_chan chan bool) (accept bool, err error) {
|
||||
|
||||
if !config.Configuration.ApiRules.Apply {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
vm := goja.New()
|
||||
|
||||
var js_script []byte
|
||||
@@ -41,5 +47,6 @@ func Apply(s interface{}, decision_chan chan bool) (accept bool, err error) {
|
||||
|
||||
accept = v.Export().(bool)
|
||||
decision_chan <- accept
|
||||
log.Infof("[rules] decision: %t", accept)
|
||||
return accept, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user