This commit is contained in:
callebtc
2022-08-31 14:09:17 +02:00
parent ef375c01bd
commit edc4152faf
13 changed files with 335 additions and 124 deletions

188
README.md
View File

@@ -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.
![electronwall0 3 2](https://user-images.githubusercontent.com/93376500/178162791-e6ba90c1-2798-471d-b7aa-0b12eae8bf2e.png)
@@ -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*

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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,6 +15,12 @@ type ApiNodeInfo struct {
}
func GetApiNodeinfo(pubkey string) (ApiNodeInfo, error) {
response := ApiNodeInfo{
OneMl: OneML_NodeInfoResponse{},
Amboss: Amboss_NodeInfoResponse{},
}
if config.Configuration.ApiRules.OneMl.Active {
// get info from 1ml
OnemlClient := GetOneMlClient()
onemlNodeInfo, err := OnemlClient.GetNodeInfo(pubkey)
@@ -19,7 +28,10 @@ func GetApiNodeinfo(pubkey string) (ApiNodeInfo, error) {
log.Errorf(err.Error())
onemlNodeInfo = OneML_NodeInfoResponse{}
}
response.OneMl = onemlNodeInfo
}
if config.Configuration.ApiRules.Amboss.Active {
// get info from amboss
ambossClient := GetAmbossClient()
ambossNodeInfo, err := ambossClient.GetNodeInfo(pubkey)
@@ -27,8 +39,7 @@ func GetApiNodeinfo(pubkey string) (ApiNodeInfo, error) {
log.Errorf(err.Error())
ambossNodeInfo = Amboss_NodeInfoResponse{}
}
return ApiNodeInfo{
OneMl: onemlNodeInfo,
Amboss: ambossNodeInfo,
}, err
response.Amboss = ambossNodeInfo
}
return response, nil
}

View File

@@ -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,

View File

@@ -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

View File

@@ -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"))

View File

@@ -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"

View File

@@ -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
View File

@@ -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)

View File

@@ -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)

View File

@@ -1,4 +1,5 @@
if (
// only forward amounts larger than 100 sat
HtlcForward.Event.OutgoingAmountMsat >= 100000
) { true } else { false }

View File

@@ -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
}