lightning-terminal/rules/channel_restrictions_test.go
bitromortac 7e0c04fea5
rules: add test to demo failure for closed channels
This also introduces the use of testify mock to check which calls were
made.
2026-02-03 15:40:13 +01:00

329 lines
8.5 KiB
Go

package rules
import (
"context"
"encoding/hex"
"fmt"
"math/rand"
"testing"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightninglabs/lightning-terminal/firewalldb"
"github.com/lightninglabs/lightning-terminal/session"
"github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
// TestChannelRestrictCheckRequest ensures that the ChannelRestrictEnforcer
// correctly accepts or denys a request.
func TestChannelRestrictCheckRequest(t *testing.T) {
txid1, index1, err := newTXID()
require.NoError(t, err)
txid2, index2, err := newTXID()
require.NoError(t, err)
txid3, index3, err := newTXID()
require.NoError(t, err)
chanPointStr1 := fmt.Sprintf("%s:%d", hex.EncodeToString(txid1), index1)
chanPointStr2 := fmt.Sprintf("%s:%d", hex.EncodeToString(txid2), index2)
chanPointStr3 := fmt.Sprintf("%s:%d", hex.EncodeToString(txid3), index3)
chanID1, _ := firewalldb.NewPseudoUint64()
chanID2, _ := firewalldb.NewPseudoUint64()
chanID3, _ := firewalldb.NewPseudoUint64()
ctx := context.Background()
mgr := NewChannelRestrictMgr()
cfg := &mockLndClient{}
cfg.On(
"ListChannels", mock.Anything, mock.Anything, mock.Anything,
mock.Anything,
).Return([]lndclient.ChannelInfo{
{
ChannelID: chanID1,
ChannelPoint: chanPointStr1,
},
{
ChannelID: chanID2,
ChannelPoint: chanPointStr2,
},
{
ChannelID: chanID3,
ChannelPoint: chanPointStr3,
},
}, nil)
enf, err := mgr.NewEnforcer(ctx, cfg, &ChannelRestrict{
DenyList: []uint64{
chanID1, chanID2,
},
})
require.NoError(t, err)
// A request for an irrelevant URI should be allowed.
_, err = enf.HandleRequest(ctx, "random-URI", nil)
require.NoError(t, err)
// If there is a channel restriction list, then no global policy updates
// are allowed.
_, err = enf.HandleRequest(
ctx, "/lnrpc.Lightning/UpdateChannelPolicy",
&lnrpc.PolicyUpdateRequest{
Scope: &lnrpc.PolicyUpdateRequest_Global{Global: true},
},
)
require.ErrorContainsf(t, err, "cant apply call to global scope when "+
"using a channel restriction list", "")
// Test that an action on channel point 1 in the string form is
// disallowed.
chanPoint1 := &lnrpc.ChannelPoint{
FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{
FundingTxidStr: hex.EncodeToString(txid1),
},
OutputIndex: index1,
}
_, err = enf.HandleRequest(
ctx, "/lnrpc.Lightning/UpdateChannelPolicy",
&lnrpc.PolicyUpdateRequest{
Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{
ChanPoint: chanPoint1,
},
},
)
require.ErrorContainsf(t, err, "illegal action on channel in channel "+
"restriction list", "")
// Test that an action on channel point 2 in the byte form is
// disallowed.
h, err := chainhash.NewHashFromStr(hex.EncodeToString(txid2))
require.NoError(t, err)
chanPoint2 := &lnrpc.ChannelPoint{
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
FundingTxidBytes: h[:],
},
OutputIndex: index2,
}
_, err = enf.HandleRequest(
ctx, "/lnrpc.Lightning/UpdateChannelPolicy",
&lnrpc.PolicyUpdateRequest{
Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{
ChanPoint: chanPoint2,
},
},
)
require.ErrorContainsf(t, err, "illegal action on channel in channel "+
"restriction list", "")
// Test that an action on a channel not in the deny-list is allowed.
chanPoint3 := &lnrpc.ChannelPoint{
FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{
FundingTxidStr: hex.EncodeToString(txid3),
},
OutputIndex: index3,
}
_, err = enf.HandleRequest(
ctx, "/lnrpc.Lightning/UpdateChannelPolicy",
&lnrpc.PolicyUpdateRequest{
Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{
ChanPoint: chanPoint3,
},
},
)
require.NoError(t, err)
}
func newTXID() ([]byte, uint32, error) {
var b [32]byte
if _, err := rand.Read(b[:]); err != nil {
return nil, 0, err
}
return b[:], rand.Uint32(), nil
}
type mockLndClient struct {
lndclient.LightningClient
Config
mock.Mock
}
func (m *mockLndClient) GetLndClient() lndclient.LightningClient {
return m
}
func (m *mockLndClient) ListChannels(ctx context.Context, public, active bool,
opts ...lndclient.ListChannelsOption) ([]lndclient.ChannelInfo, error) {
args := m.Called(ctx, public, active, opts)
if args.Get(0) != nil {
return args.Get(0).([]lndclient.ChannelInfo), args.Error(1)
}
return nil, args.Error(1)
}
// TestChannelRestrictRealToPseudo tests that the ChannelRestrict's RealToPseudo
// method correctly determines which real strings to generate pseudo pairs for
// based on the privacy map db passed to it.
func TestChannelRestrictRealToPseudo(t *testing.T) {
t.Parallel()
ctx := context.Background()
chanID1 := firewalldb.Uint64ToStr(1)
chanID2 := firewalldb.Uint64ToStr(2)
chanID3 := firewalldb.Uint64ToStr(3)
chanID2Obfuscated := firewalldb.Uint64ToStr(200)
tests := []struct {
name string
privacyFlags session.PrivacyFlags
dbPreLoad map[string]string
expectNewPairs map[string]bool
}{
{
// If there is no preloaded DB, then we expect all the
// values in the deny list to be returned from the
// RealToPseudo method.
name: "no pre loaded db",
expectNewPairs: map[string]bool{
chanID1: true,
chanID2: true,
chanID3: true,
},
},
{
// If the DB is preloaded with an entry for "channel 2"
// then we don't expect that entry to be returned in the
// set of new pairs.
name: "partially pre-loaded DB",
dbPreLoad: map[string]string{
chanID2: chanID2Obfuscated,
},
expectNewPairs: map[string]bool{
chanID1: true,
chanID3: true,
},
},
{
name: "turned off mapping",
privacyFlags: session.PrivacyFlags{
session.ClearChanIDs,
},
},
}
// Construct the ChannelRestrict deny list. Note that we repeat one of
// the entries here in order to ensure that the RealToPseudo method is
// forced to look up any real-to-pseudo pairs that it already
// generated.
cr := &ChannelRestrict{
DenyList: []uint64{
1,
2,
3,
3,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
privMapPairDB := firewalldb.NewPrivacyMapPairs(
test.dbPreLoad,
)
// Iterate over the preload key value pairs and load
// them into the DB.
expectedDenyList := make(map[uint64]bool)
for _, p := range test.dbPreLoad {
// Add the pseudo value to the expected deny
// list.
pInt, err := firewalldb.StrToUint64(p)
require.NoError(t, err)
expectedDenyList[pInt] = true
}
// Call the RealToPseudo method on the ChannelRestrict
// rule. This will return the rule value in its pseudo
// form along with any new privacy map pairs that should
// be added to the DB.
v, newPairs, err := cr.RealToPseudo(
ctx, privMapPairDB, test.privacyFlags,
)
require.NoError(t, err)
require.Len(t, newPairs, len(test.expectNewPairs))
// We add each new pair to the expected deny list too.
for r, p := range newPairs {
require.True(t, test.expectNewPairs[r])
pInt, err := firewalldb.StrToUint64(p)
require.NoError(t, err)
expectedDenyList[pInt] = true
}
denyList, ok := v.(*ChannelRestrict)
require.True(t, ok)
// We expect the original deny list if we switch off
// privacy mapping.
if test.privacyFlags.Contains(
session.ClearChanIDs) {
for _, p := range cr.DenyList {
expectedDenyList[p] = true
}
}
// Assert that the resulting deny list is the same
// length as the un-obfuscated one.
require.Len(t, denyList.DenyList, len(cr.DenyList))
// Now iterate over the deny list and assert that each
// value appears in our expected deny list.
for _, channel := range denyList.DenyList {
require.True(t, expectedDenyList[channel])
}
})
}
}
// TestChannelRestrictResilience ensures that the ChannelRestrictEnforcer is
// resilient to missing channels during initialization.
func TestChannelRestrictResilience(t *testing.T) {
var (
ctx = context.Background()
mgr = NewChannelRestrictMgr()
)
chanID1, _ := firewalldb.NewPseudoUint64()
// Initially, LND has no channels.
cfg := &mockLndClient{}
cfg.On(
"ListChannels", mock.Anything, mock.Anything, mock.Anything,
mock.Anything,
).Return([]lndclient.ChannelInfo{}, nil)
// We create an enforcer that denies chanID1 (maybe a closed channel or
// generally unknown). This will be fixed in a future commit.
_, err := mgr.NewEnforcer(ctx, cfg, &ChannelRestrict{
DenyList: []uint64{chanID1},
})
require.ErrorContains(t, err, "invalid channel ID")
cfg.AssertExpectations(t)
}