mirror of
https://github.com/lightninglabs/lightning-terminal.git
synced 2026-08-13 12:33:36 +02:00
- Replace occurrences of `// nolint:lll` with `// nolint:ll` across files for consistency. - Reformat multiline strings, comments, and function parameters to improve clarity and adhere to style guidelines. - Add `// nolint:ll` comments where necessary to prevent linter warnings.
413 lines
11 KiB
Go
413 lines
11 KiB
Go
package rules
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/lightninglabs/lightning-terminal/firewalldb"
|
|
"github.com/lightninglabs/lightning-terminal/litrpc"
|
|
mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware"
|
|
"github.com/lightninglabs/lightning-terminal/session"
|
|
"github.com/lightningnetwork/lnd/lnrpc"
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
var (
|
|
// Compile-time checks to ensure that ChannelRestrict,
|
|
// ChannelRestrictMgr and ChannelRestrictEnforcer implement the
|
|
// appropriate Manager, Enforcer and Values interface.
|
|
_ Manager = (*ChannelRestrictMgr)(nil)
|
|
_ Enforcer = (*ChannelRestrictEnforcer)(nil)
|
|
_ Values = (*ChannelRestrict)(nil)
|
|
)
|
|
|
|
// ChannelRestrictName is the string identifier of the ChannelRestrict rule.
|
|
const ChannelRestrictName = "channel-restriction"
|
|
|
|
// ChannelRestrictMgr manages the ChannelRestrict rule.
|
|
type ChannelRestrictMgr struct {
|
|
// here we can have known chanID to ChanOutpoint map (and vice versa)
|
|
// then in NewEnforcer, if chan comes in we dont know of, then we
|
|
// refresh the maps.
|
|
|
|
// chanIDToPoint is a map from channel ID to channel points for our
|
|
// known set of channels.
|
|
chanIDToPoint map[uint64]string
|
|
|
|
// chanPointToID is a map from channel point to channel ID's for our
|
|
// known set of channels.
|
|
chanPointToID map[string]uint64
|
|
mu sync.Mutex
|
|
}
|
|
|
|
// NewChannelRestrictMgr constructs a new instance of a ChannelRestrictMgr.
|
|
func NewChannelRestrictMgr() *ChannelRestrictMgr {
|
|
return &ChannelRestrictMgr{
|
|
chanIDToPoint: make(map[uint64]string),
|
|
chanPointToID: make(map[string]uint64),
|
|
}
|
|
}
|
|
|
|
// Stop cleans up the resources held by the manager.
|
|
//
|
|
// NOTE: This is part of the Manager interface.
|
|
func (c *ChannelRestrictMgr) Stop() error {
|
|
return nil
|
|
}
|
|
|
|
// NewEnforcer constructs a new ChannelRestrict rule enforcer using the passed
|
|
// values and config.
|
|
//
|
|
// NOTE: This is part of the Manager interface.
|
|
func (c *ChannelRestrictMgr) NewEnforcer(ctx context.Context, cfg Config,
|
|
values Values) (Enforcer, error) {
|
|
|
|
channels, ok := values.(*ChannelRestrict)
|
|
if !ok {
|
|
return nil, fmt.Errorf("values must be of type "+
|
|
"ChannelRestrict, got %T", values)
|
|
}
|
|
|
|
chanMap := make(map[uint64]bool, len(channels.DenyList))
|
|
for _, chanID := range channels.DenyList {
|
|
chanMap[chanID] = true
|
|
err := c.maybeUpdateChannelMaps(ctx, cfg, chanID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return &ChannelRestrictEnforcer{
|
|
mgr: c,
|
|
ChannelRestrict: channels,
|
|
channelMap: chanMap,
|
|
}, nil
|
|
}
|
|
|
|
// NewValueFromProto converts the given proto value into a ChannelRestrict Value
|
|
// object.
|
|
//
|
|
// NOTE: This is part of the Manager interface.
|
|
func (c *ChannelRestrictMgr) NewValueFromProto(v *litrpc.RuleValue) (Values,
|
|
error) {
|
|
|
|
rv, ok := v.Value.(*litrpc.RuleValue_ChannelRestrict)
|
|
if !ok {
|
|
return nil, fmt.Errorf("incorrect RuleValue type")
|
|
}
|
|
|
|
chanIDs := rv.ChannelRestrict.ChannelIds
|
|
|
|
if len(chanIDs) == 0 {
|
|
return nil, fmt.Errorf("channel restrict list cannot be " +
|
|
"empty. If no channel restrictions should be applied " +
|
|
"then there is no need to add the rule")
|
|
}
|
|
|
|
return &ChannelRestrict{
|
|
DenyList: chanIDs,
|
|
}, nil
|
|
}
|
|
|
|
// EmptyValue returns a new ChannelRestrict instance.
|
|
//
|
|
// NOTE: This is part of the Manager interface.
|
|
func (c *ChannelRestrictMgr) EmptyValue() Values {
|
|
return &ChannelRestrict{}
|
|
}
|
|
|
|
// maybeUpdateChannelMaps updates the ChannelRestrictMgrs set of known channels
|
|
// iff the channel given by the caller is not found in the current map set.
|
|
func (c *ChannelRestrictMgr) maybeUpdateChannelMaps(ctx context.Context,
|
|
cfg Config, chanID uint64) error {
|
|
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
// If we already know of this channel, we don't need to go update our
|
|
// maps.
|
|
_, ok := c.chanIDToPoint[chanID]
|
|
if ok {
|
|
return nil
|
|
}
|
|
|
|
// Fetch a list of our open channels from LND.
|
|
lnd := cfg.GetLndClient()
|
|
chans, err := lnd.ListChannels(ctx, false, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var (
|
|
found bool
|
|
point string
|
|
id uint64
|
|
)
|
|
|
|
// Update our set of maps and also make sure that the channel specified
|
|
// by the caller is valid given our set of open channels.
|
|
for _, channel := range chans {
|
|
point = channel.ChannelPoint
|
|
id = channel.ChannelID
|
|
|
|
c.chanPointToID[point] = id
|
|
c.chanIDToPoint[id] = point
|
|
|
|
if id == chanID {
|
|
found = true
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
return fmt.Errorf("invalid channel ID")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *ChannelRestrictMgr) getChannelID(point string) (uint64, bool) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
id, ok := c.chanPointToID[point]
|
|
return id, ok
|
|
}
|
|
|
|
// ChannelRestrictEnforcer enforces requests and responses against a
|
|
// ChannelRestrict rule.
|
|
type ChannelRestrictEnforcer struct {
|
|
mgr *ChannelRestrictMgr
|
|
*ChannelRestrict
|
|
channelMap map[uint64]bool
|
|
}
|
|
|
|
// HandleRequest checks the validity of a request using the ChannelRestrict
|
|
// rpcmiddleware.RoundTripCheckers.
|
|
//
|
|
// NOTE: this is part of the Enforcer interface.
|
|
func (c *ChannelRestrictEnforcer) HandleRequest(ctx context.Context, uri string,
|
|
msg proto.Message) (proto.Message, error) {
|
|
|
|
checkers := c.checkers()
|
|
if checkers == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
checker, ok := checkers[uri]
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
|
|
if !checker.HandlesRequest(msg.ProtoReflect().Type()) {
|
|
return nil, fmt.Errorf("invalid implementation, checker for "+
|
|
"URI %s does not accept request of type %v", uri,
|
|
msg.ProtoReflect().Type())
|
|
}
|
|
|
|
return checker.HandleRequest(ctx, msg)
|
|
}
|
|
|
|
// HandleResponse handles a response using the ChannelRestrict
|
|
// rpcmiddleware.RoundTripCheckers.
|
|
//
|
|
// NOTE: this is part of the Enforcer interface.
|
|
func (c *ChannelRestrictEnforcer) HandleResponse(ctx context.Context,
|
|
uri string, msg proto.Message) (proto.Message, error) {
|
|
|
|
checkers := c.checkers()
|
|
if checkers == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
checker, ok := checkers[uri]
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
|
|
if !checker.HandlesResponse(msg.ProtoReflect().Type()) {
|
|
return nil, fmt.Errorf("invalid implementation, checker for "+
|
|
"URI %s does not accept response of type %v", uri,
|
|
msg.ProtoReflect().Type())
|
|
}
|
|
|
|
return checker.HandleResponse(ctx, msg)
|
|
}
|
|
|
|
// HandleErrorResponse handles and possible alters an error. This is a noop for
|
|
// the ChannelRestrict rule.
|
|
//
|
|
// NOTE: this is part of the Enforcer interface.
|
|
func (c *ChannelRestrictEnforcer) HandleErrorResponse(_ context.Context,
|
|
_ string, _ error) (error, error) {
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
// checkers returns a map of URI to rpcmiddleware.RoundTripChecker which define
|
|
// how the URI should be handled.
|
|
func (c *ChannelRestrictEnforcer) checkers() map[string]mid.RoundTripChecker {
|
|
return map[string]mid.RoundTripChecker{
|
|
"/lnrpc.Lightning/UpdateChannelPolicy": mid.NewRequestChecker(
|
|
&lnrpc.PolicyUpdateRequest{},
|
|
&lnrpc.PolicyUpdateResponse{},
|
|
func(ctx context.Context,
|
|
r *lnrpc.PolicyUpdateRequest) error {
|
|
|
|
if r.GetGlobal() {
|
|
return fmt.Errorf("cant apply call " +
|
|
"to global scope when using " +
|
|
"a channel restriction list")
|
|
}
|
|
|
|
chanPoint := r.GetChanPoint()
|
|
if chanPoint == nil {
|
|
return fmt.Errorf("no channel point " +
|
|
"specified")
|
|
}
|
|
|
|
txid, err := lnrpc.GetChanPointFundingTxid(
|
|
chanPoint,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
index := chanPoint.GetOutputIndex()
|
|
point := fmt.Sprintf(
|
|
"%s:%d", txid.String(), index,
|
|
)
|
|
|
|
id, ok := c.mgr.getChannelID(point)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
if c.channelMap[id] {
|
|
return fmt.Errorf("illegal action on " +
|
|
"channel in channel " +
|
|
"restriction list")
|
|
}
|
|
|
|
return nil
|
|
},
|
|
),
|
|
}
|
|
}
|
|
|
|
// ChannelRestrict is a rule prevents calls from acting upon a given set of
|
|
// channels.
|
|
type ChannelRestrict struct {
|
|
// DenyList is a list of SCIDs that should not be acted upon by
|
|
// any call.
|
|
DenyList []uint64 `json:"channel_deny_list"`
|
|
}
|
|
|
|
// VerifySane checks that the value of the values is ok given the min and max
|
|
// allowed values. This is a noop for the ChannelRestrict rule.
|
|
//
|
|
// NOTE: this is part of the Values interface.
|
|
func (c *ChannelRestrict) VerifySane(_, _ Values) error {
|
|
return nil
|
|
}
|
|
|
|
// RuleName returns the name of the rule that these values are to be used with.
|
|
//
|
|
// NOTE: this is part of the Values interface.
|
|
func (c *ChannelRestrict) RuleName() string {
|
|
return ChannelRestrictName
|
|
}
|
|
|
|
// ToProto converts the rule Values to the litrpc counterpart.
|
|
//
|
|
// NOTE: this is part of the Values interface.
|
|
func (c *ChannelRestrict) ToProto() *litrpc.RuleValue {
|
|
return &litrpc.RuleValue{
|
|
Value: &litrpc.RuleValue_ChannelRestrict{
|
|
ChannelRestrict: &litrpc.ChannelRestrict{
|
|
ChannelIds: c.DenyList,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// PseudoToReal assumes that the deny-list contains pseudo channel IDs and uses
|
|
// these to check the privacy map db for the corresponding real channel IDs.
|
|
// It constructs a new ChannelRestrict instance with these real channel IDs.
|
|
//
|
|
// NOTE: this is part of the Values interface.
|
|
func (c *ChannelRestrict) PseudoToReal(ctx context.Context,
|
|
db firewalldb.PrivacyMapDB, flags session.PrivacyFlags) (Values,
|
|
error) {
|
|
|
|
restrictList := make([]uint64, len(c.DenyList))
|
|
|
|
// We don't obfuscate the channel IDs if the channel id flag is set.
|
|
if flags.Contains(session.ClearChanIDs) {
|
|
copy(restrictList, c.DenyList)
|
|
|
|
return &ChannelRestrict{DenyList: restrictList}, nil
|
|
}
|
|
|
|
err := db.View(ctx, func(ctx context.Context,
|
|
tx firewalldb.PrivacyMapTx) error {
|
|
|
|
for i, chanID := range c.DenyList {
|
|
real, err := firewalldb.RevealUint64(ctx, tx, chanID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
restrictList[i] = real
|
|
}
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &ChannelRestrict{DenyList: restrictList}, nil
|
|
}
|
|
|
|
// RealToPseudo converts all the real channel IDs into pseudo IDs. It returns a
|
|
// map of any new real to pseudo strings that should be persisted that it did
|
|
// not find in the given PrivacyMapReader.
|
|
//
|
|
// NOTE: this is part of the Values interface.
|
|
func (c *ChannelRestrict) RealToPseudo(_ context.Context,
|
|
db firewalldb.PrivacyMapReader,
|
|
flags session.PrivacyFlags) (Values, map[string]string, error) {
|
|
|
|
pseudoIDs := make([]uint64, len(c.DenyList))
|
|
privMapPairs := make(map[string]string)
|
|
|
|
// We don't obfuscate the channel IDs if the channel id flag is set.
|
|
if flags.Contains(session.ClearChanIDs) {
|
|
copy(pseudoIDs, c.DenyList)
|
|
|
|
return &ChannelRestrict{DenyList: pseudoIDs}, privMapPairs, nil
|
|
}
|
|
|
|
for i, c := range c.DenyList {
|
|
// TODO(elle): check that this channel actually exists
|
|
|
|
chanID := firewalldb.Uint64ToStr(c)
|
|
pseudo, ok := pseudoFromReal(db, privMapPairs, chanID)
|
|
if ok {
|
|
p, err := firewalldb.StrToUint64(pseudo)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
pseudoIDs[i] = p
|
|
continue
|
|
}
|
|
|
|
pseudoCp, pseudoCpStr := firewalldb.NewPseudoUint64()
|
|
privMapPairs[chanID] = pseudoCpStr
|
|
pseudoIDs[i] = pseudoCp
|
|
}
|
|
|
|
return &ChannelRestrict{DenyList: pseudoIDs}, privMapPairs, nil
|
|
}
|