Merge pull request #10 from callebtc/api/1ml

javascript and api
This commit is contained in:
calle
2022-08-31 14:49:17 +02:00
committed by GitHub
18 changed files with 742 additions and 104 deletions

190
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)
@@ -21,10 +23,192 @@ go build .
You can download a binary for your system [here](https://github.com/callebtc/electronwall/releases). You'll still need a [config file](https://github.com/callebtc/electronwall/blob/main/config.yaml.example).
## Config
Edit `config.yaml.example` and rename to `config.yaml`.
Edit `config.yaml.example` and rename to `config.yaml`.
## Run
```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*

77
api/1ml.go Normal file
View File

@@ -0,0 +1,77 @@
package api
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"time"
"github.com/callebtc/electronwall/config"
log "github.com/sirupsen/logrus"
)
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"`
}
type OneMlClient struct {
}
func GetOneMlClient() OneMlClient {
return OneMlClient{}
}
func (c *OneMlClient) GetNodeInfo(pubkey string) (OneML_NodeInfoResponse, error) {
url := fmt.Sprintf("https://1ml.com/node/%s/json", pubkey)
log.Infof("Getting info from 1ml.com for %s", pubkey)
client := http.Client{
Timeout: time.Second * time.Duration(config.Configuration.ApiRules.OneMl.Timeout),
}
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
log.Fatal(err)
}
res, getErr := client.Do(req)
if getErr != nil {
log.Fatal(getErr)
}
if res.Body != nil {
defer res.Body.Close()
}
body, readErr := ioutil.ReadAll(res.Body)
if readErr != nil {
log.Fatal(readErr)
}
r := OneML_NodeInfoResponse{}
jsonErr := json.Unmarshal(body, &r)
if jsonErr != nil {
log.Errorf("[1ml] api error: %v", jsonErr)
}
return r, nil
}

169
api/amboss.go Normal file
View File

@@ -0,0 +1,169 @@
package api
import (
"context"
"time"
"github.com/callebtc/electronwall/config"
"github.com/machinebox/graphql"
log "github.com/sirupsen/logrus"
)
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"`
}
type Amboss_NodeInfoResponse_Nested struct {
Data struct {
GetNode struct {
Amboss_NodeInfoResponse
} `json:"getNode"`
} `json:"data"`
}
var amboss_graphql_query = `query Info($pubkey: String!) {
getNode(pubkey: $pubkey) {
socials {
info {
email
telegram
twitter
lightning_address
website
pubkey
minChannelSize
message
twitter_verified
updated
}
}
graph_info {
last_update
metrics {
capacity
capacity_rank
channels
channels_rank
}
node {
addresses {
addr
ip_info {
city
country
country_code
}
network
}
last_update
color
features {
feature_id
is_known
is_required
name
}
}
}
amboss {
is_favorite
is_prime
number_favorites
new_channel_gossip_delta {
mean
sd
}
notifications {
number_subscribers
}
}
}
}`
var amboss_graphql_variabnes = `{
"pubkey": "%s"
}
`
type AmbossClient struct {
}
func GetAmbossClient() AmbossClient {
return AmbossClient{}
}
func (c *AmbossClient) GetNodeInfo(pubkey string) (Amboss_NodeInfoResponse, error) {
url := "https://api.amboss.space/graphql"
log.Infof("Getting info from amboss.space for %s", pubkey)
graphqlClient := graphql.NewClient(url)
graphqlRequest := graphql.NewRequest(amboss_graphql_query)
graphqlRequest.Var("pubkey", pubkey)
// set header fields
graphqlRequest.Header.Set("Cache-Control", "no-cache")
graphqlRequest.Header.Set("Content-Type", "application/json")
var r_nested Amboss_NodeInfoResponse_Nested
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)
}
r := r_nested.Data.GetNode.Amboss_NodeInfoResponse
return r, nil
}

45
api/api.go Normal file
View File

@@ -0,0 +1,45 @@
package api
import (
"github.com/callebtc/electronwall/config"
log "github.com/sirupsen/logrus"
)
type ApiClient interface {
GetNodeInfo(pubkey string) OneML_NodeInfoResponse
}
type ApiNodeInfo struct {
OneMl OneML_NodeInfoResponse `json:"1ml"`
Amboss Amboss_NodeInfoResponse `json:"amboss"`
}
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)
if err != nil {
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)
if err != nil {
log.Errorf(err.Error())
ambossNodeInfo = Amboss_NodeInfoResponse{}
}
response.Amboss = ambossNodeInfo
}
return response, nil
}

51
api_test/main.go Normal file
View File

@@ -0,0 +1,51 @@
package main
import (
"os"
"github.com/callebtc/electronwall/api"
"github.com/callebtc/electronwall/rules"
"github.com/callebtc/electronwall/types"
"github.com/lightningnetwork/lnd/lnrpc"
log "github.com/sirupsen/logrus"
)
func main() {
if len(os.Args) < 2 {
log.Errorf("Pass node ID as argument.")
return
}
pubkey := os.Args[1]
log.Infof("pubkey: %s", pubkey)
pk_byte := []byte(pubkey)
decision_chan := make(chan bool, 1)
event := types.ChannelAcceptEvent{}
event.Event = &lnrpc.ChannelAcceptRequest{}
event.Event.FundingAmt = 1_000_000
log.Infof("Funding amount: %d sat", event.Event.FundingAmt)
event.Event.NodePubkey = pk_byte
req := lnrpc.ChannelAcceptRequest{}
req.NodePubkey = pk_byte
nodeInfo, err := api.GetApiNodeinfo(string(req.NodePubkey))
if err != nil {
log.Errorf(err.Error())
}
// log.Infoln("1ML")
// // log.Infoln("%+v", noeInfo.OneMl)
// log.Printf("%+v\n", nodeInfo.OneMl)
// log.Infoln("Amboss")
// log.Printf("%+v\n", nodeInfo.Amboss)
event.OneMl = nodeInfo.OneMl
event.Amboss = nodeInfo.Amboss
rules_decision, err := rules.Apply(event, decision_chan)
if err != nil {
panic(err)
}
log.Infof("Decision: %t", rules_decision)
}

View File

@@ -6,13 +6,15 @@ import (
"fmt"
"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"
log "github.com/sirupsen/logrus"
)
func (app *App) getChannelAcceptEvent(ctx context.Context, req lnrpc.ChannelAcceptRequest) (types.ChannelAcceptEvent, error) {
func (app *App) GetChannelAcceptEvent(ctx context.Context, req lnrpc.ChannelAcceptRequest) (types.ChannelAcceptEvent, error) {
// print the incoming channel request
alias, err := app.lnd.getNodeAlias(ctx, hex.EncodeToString(req.NodePubkey))
if err != nil {
@@ -23,11 +25,19 @@ func (app *App) getChannelAcceptEvent(ctx context.Context, req lnrpc.ChannelAcce
if err != nil {
log.Errorf(err.Error())
}
noeInfo, err := api.GetApiNodeinfo(hex.EncodeToString(req.NodePubkey))
if err != nil {
log.Errorf(err.Error())
}
return types.ChannelAcceptEvent{
PubkeyFrom: hex.EncodeToString(req.NodePubkey),
AliasFrom: alias,
NodeInfo: info,
Event: &req,
OneMl: noeInfo.OneMl,
Amboss: noeInfo.Amboss,
}, nil
}
@@ -68,7 +78,7 @@ func (app *App) interceptChannelEvents(ctx context.Context) error {
return err
}
channelAcceptEvent, err := app.getChannelAcceptEvent(ctx, req)
channelAcceptEvent, err := app.GetChannelAcceptEvent(ctx, req)
if err != nil {
return err
}
@@ -116,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
}
@@ -128,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)
@@ -143,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 {
@@ -159,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(hex.EncodeToString(req.NodePubkey))
for _, pubkey := range listToParse {
if hex.EncodeToString(req.NodePubkey) == pubkey || pubkey == "*" {
accept = !accept
break
}
}
log.Infof("[list] decision: %t", accept)
return accept, nil
}
@@ -205,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"))

4
go.mod
View File

@@ -6,8 +6,12 @@ require (
github.com/dop251/goja v0.0.0-20220815083517-0c74f9139fd6
github.com/jinzhu/configor v1.2.1
github.com/lightningnetwork/lnd v0.14.3-beta
github.com/machinebox/graphql v0.2.2
github.com/matryer/is v1.4.0 // indirect
github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.7.0
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 // indirect
golang.org/x/tools v0.1.10 // indirect
google.golang.org/grpc v1.46.2
gopkg.in/macaroon.v2 v2.1.0
)

16
go.sum
View File

@@ -527,9 +527,13 @@ github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQ
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw=
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796/go.mod h1:3p7ZTf9V1sNPI5H8P3NkTFF4LuwMdPl2DodF60qAKqY=
github.com/ltcsuite/ltcutil v0.0.0-20181217130922-17f3b04680b6/go.mod h1:8Vg/LTOO0KYa/vlHWJ6XZAevPQThGH5sufO0Hrou/lA=
github.com/machinebox/graphql v0.2.2 h1:dWKpJligYKhYKO5A2gvNhkJdQMNZeChZYyBbrZkBZfo=
github.com/machinebox/graphql v0.2.2/go.mod h1:F+kbVMHuwrQ5tYgU9JXlnskM8nOaFxCAEolaQybkjWA=
github.com/magiconair/properties v1.7.6/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
@@ -721,6 +725,7 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
@@ -842,6 +847,7 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/net v0.0.0-20170915142106-8351a756f30f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -887,8 +893,9 @@ golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210913180222-943fd674d43e h1:+b/22bPvDYt4NPDcy4xAGCmON713ONAWFeY3Z7I3tR8=
golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -968,8 +975,10 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210915083310-ed5796bab164 h1:7ZDGnxgHAMw7thfC5bEos0RDAccZKxioiWBhfIe+tvw=
golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 h1:OH54vjqzRWmbJ62fjuhxy7AxFFgoHN0/DPc/UrL8cAs=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -1052,8 +1061,9 @@ golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

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,8 +2,10 @@ package main
import (
"context"
"encoding/hex"
"testing"
"github.com/callebtc/electronwall/config"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
log "github.com/sirupsen/logrus"
@@ -33,8 +35,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 +69,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 +103,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 +139,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 +178,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 +214,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 +248,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 +281,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 +316,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 +350,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 +384,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 +418,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,
@@ -451,16 +453,16 @@ func TestChannelAllowlist_CorrectKey(t *testing.T) {
defer cancel()
app := NewApp(ctx, client)
Configuration.ChannelMode = "allowlist"
Configuration.ChannelAllowlist = []string{"6d792d7075626b65792d69732d766572792d6c6f6e672d666f722d7472696d6d696e672d7075626b6579"}
pubkey_str := "03006fcf3312dae8d068ea297f58e2bd00ec1ffe214b793eda46966b6294a53ce6"
config.Configuration.ChannelMode = "allowlist"
config.Configuration.ChannelAllowlist = []string{pubkey_str}
app.DispatchChannelAcceptor(ctx)
// correct key: should be allowed
pubkey, _ := hex.DecodeString(pubkey_str)
client.channelAcceptorRequests <- &lnrpc.ChannelAcceptRequest{
NodePubkey: []byte("my-pubkey-is-very-long-for-trimming-pubkey"),
NodePubkey: pubkey,
FundingAmt: 1337000,
PendingChanId: []byte("759495353533530113"),
}
@@ -468,15 +470,28 @@ func TestChannelAllowlist_CorrectKey(t *testing.T) {
resp := <-client.channelAcceptorResponses
require.Equal(t, resp.Accept, true)
}
func TestChannelAllowlist_WrongKey(t *testing.T) {
client := newLndclientMock()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
app := NewApp(ctx, client)
pubkey_str := "03006fcf3312dae8d068ea297f58e2bd00ec1ffe214b793eda46966b6294a53ce6"
config.Configuration.ChannelMode = "allowlist"
config.Configuration.ChannelAllowlist = []string{pubkey_str}
app.DispatchChannelAcceptor(ctx)
// wrong key: should be denied
client.channelAcceptorRequests <- &lnrpc.ChannelAcceptRequest{
NodePubkey: []byte("WRONG PUBKEY"),
NodePubkey: []byte("WRONG-KEY"),
FundingAmt: 1337000,
PendingChanId: []byte("759495353533530113"),
}
resp = <-client.channelAcceptorResponses
resp := <-client.channelAcceptorResponses
require.Equal(t, resp.Accept, false)
}
func TestChannelAllowlist_Wildcard(t *testing.T) {
@@ -488,12 +503,14 @@ func TestChannelAllowlist_Wildcard(t *testing.T) {
app.DispatchChannelAcceptor(ctx)
pubkey_str := "03006fcf3312dae8d068ea297f58e2bd00ec1ffe214b793eda46966b6294a53ce6"
// wildcard: should be allowed
Configuration.ChannelMode = "allowlist"
Configuration.ChannelAllowlist = []string{"*"}
config.Configuration.ChannelMode = "allowlist"
config.Configuration.ChannelAllowlist = []string{"*"}
pubkey, _ := hex.DecodeString(pubkey_str)
client.channelAcceptorRequests <- &lnrpc.ChannelAcceptRequest{
NodePubkey: []byte("WRONG PUBKEY"),
NodePubkey: pubkey,
FundingAmt: 1337000,
PendingChanId: []byte("759495353533530113"),
}
@@ -508,16 +525,16 @@ func TestChannelDenylist_Match(t *testing.T) {
defer cancel()
app := NewApp(ctx, client)
Configuration.ChannelMode = "denylist"
Configuration.ChannelDenylist = []string{"6d792d7075626b65792d69732d766572792d6c6f6e672d666f722d7472696d6d696e672d7075626b6579"}
pubkey_str := "03006fcf3312dae8d068ea297f58e2bd00ec1ffe214b793eda46966b6294a53ce6"
config.Configuration.ChannelMode = "denylist"
config.Configuration.ChannelDenylist = []string{pubkey_str}
app.DispatchChannelAcceptor(ctx)
// should be denied
pubkey, _ := hex.DecodeString(pubkey_str)
client.channelAcceptorRequests <- &lnrpc.ChannelAcceptRequest{
NodePubkey: []byte("my-pubkey-is-very-long-for-trimming-pubkey"),
NodePubkey: []byte(pubkey),
FundingAmt: 1337000,
PendingChanId: []byte("759495353533530113"),
}
@@ -532,15 +549,16 @@ func TestChannelAllowlist_Match(t *testing.T) {
defer cancel()
app := NewApp(ctx, client)
Configuration.ChannelMode = "allowlist"
Configuration.ChannelAllowlist = []string{"6d792d7075626b65792d69732d766572792d6c6f6e672d666f722d7472696d6d696e672d7075626b6579"}
pubkey_str := "03006fcf3312dae8d068ea297f58e2bd00ec1ffe214b793eda46966b6294a53ce6"
config.Configuration.ChannelMode = "allowlist"
config.Configuration.ChannelAllowlist = []string{pubkey_str}
app.DispatchChannelAcceptor(ctx)
// should be allowed
pubkey, _ := hex.DecodeString(pubkey_str)
client.channelAcceptorRequests <- &lnrpc.ChannelAcceptRequest{
NodePubkey: []byte("my-pubkey-is-very-long-for-trimming-pubkey"),
NodePubkey: []byte(pubkey),
FundingAmt: 1337000,
PendingChanId: []byte("759495353533530113"),
}

View File

@@ -1,2 +1,20 @@
ChannelAccept.Event.FundingAmt >= 750000;
// 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
)

View File

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

View File

@@ -2,14 +2,20 @@ package rules
import (
"fmt"
"log"
"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
@@ -28,12 +34,6 @@ func Apply(s interface{}, decision_chan chan bool) (accept bool, err error) {
if err != nil {
log.Fatal(err)
}
// case *routerrpc.ForwardHtlcInterceptRequest:
// vm.Set("HtlcForwardEvent", s)
// js_script, err = os.ReadFile("rules/ForwardHtlcInterceptRequest.js")
// if err != nil {
// log.Fatal(err)
// }
default:
return false, fmt.Errorf("no rule found for event type")
}
@@ -41,11 +41,12 @@ func Apply(s interface{}, decision_chan chan bool) (accept bool, err error) {
// execute script
v, err := vm.RunString(string(js_script))
if err != nil {
fmt.Print(err.Error())
log.Errorf("JS error: %v", err)
return
}
accept = v.Export().(bool)
decision_chan <- accept
log.Infof("[rules] decision: %t", accept)
return accept, nil
}

24
types/types.go Normal file
View File

@@ -0,0 +1,24 @@
package types
import (
"github.com/callebtc/electronwall/api"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
)
type HtlcForwardEvent struct {
PubkeyFrom string
AliasFrom string
PubkeyTo string
AliasTo string
Event *routerrpc.ForwardHtlcInterceptRequest
}
type ChannelAcceptEvent struct {
PubkeyFrom string
AliasFrom string
Event *lnrpc.ChannelAcceptRequest
NodeInfo *lnrpc.NodeInfo
OneMl api.OneML_NodeInfoResponse
Amboss api.Amboss_NodeInfoResponse
}