Skip to content

Commit ad9dd3e

Browse files
committed
rules: add ChanPolicyBounds rule
1 parent 042a5ee commit ad9dd3e

File tree

3 files changed

+721
-1
lines changed

3 files changed

+721
-1
lines changed

rules/chan_policy_bounds.go

Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
package rules
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"math"
7+
8+
"github.com/lightninglabs/lightning-terminal/firewalldb"
9+
"github.com/lightninglabs/lightning-terminal/litrpc"
10+
mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware"
11+
"github.com/lightningnetwork/lnd/lnrpc"
12+
"google.golang.org/protobuf/proto"
13+
)
14+
15+
var (
16+
// Compile-time checks to ensure that ChanPolicyBounds and
17+
// ChanPolicyBoundsMgr implement the appropriate Manager, Enforcer and
18+
// Values interface.
19+
_ Manager = (*ChanPolicyBoundsMgr)(nil)
20+
_ Enforcer = (*ChanPolicyBounds)(nil)
21+
_ Values = (*ChanPolicyBounds)(nil)
22+
)
23+
24+
// ChanPolicyBoundsName is the string identifier of the ChanPolicyBounds rule.
25+
const ChanPolicyBoundsName = "channel-policy-bounds"
26+
27+
// ChanPolicyBoundsMgr manages the ChanPolicyBounds rule.
28+
type ChanPolicyBoundsMgr struct{}
29+
30+
// Stop cleans up the resources held by the manager.
31+
//
32+
// NOTE: This is part of the Manager interface.
33+
func (b *ChanPolicyBoundsMgr) Stop() error {
34+
return nil
35+
}
36+
37+
// NewEnforcer constructs a new ChanPolicyBounds rule enforcer using the passed
38+
// values and config.
39+
//
40+
// NOTE: This is part of the Manager interface.
41+
func (b *ChanPolicyBoundsMgr) NewEnforcer(_ Config, values Values) (Enforcer,
42+
error) {
43+
44+
bounds, ok := values.(*ChanPolicyBounds)
45+
if !ok {
46+
return nil, fmt.Errorf("values must be of type "+
47+
"ChanPolicyBounds, got %T", values)
48+
}
49+
50+
return bounds, nil
51+
}
52+
53+
// NewValueFromProto converts the given proto value into a ChanPolicyBounds
54+
// Value object.
55+
//
56+
// NOTE: This is part of the Manager interface.
57+
func (b *ChanPolicyBoundsMgr) NewValueFromProto(value *litrpc.RuleValue) (
58+
Values, error) {
59+
60+
rv, ok := value.Value.(*litrpc.RuleValue_ChanPolicyBounds)
61+
if !ok {
62+
return nil, fmt.Errorf("incorrect RuleValue type")
63+
}
64+
65+
policyBounds := rv.ChanPolicyBounds
66+
67+
return &ChanPolicyBounds{
68+
MinBaseMsat: policyBounds.MinBaseMsat,
69+
MaxBaseMsat: policyBounds.MaxBaseMsat,
70+
MinRatePPM: policyBounds.MinRatePpm,
71+
MaxRatePPM: policyBounds.MaxRatePpm,
72+
MinCLTVDelta: policyBounds.MinCltvDelta,
73+
MaxCLTVDelta: policyBounds.MaxCltvDelta,
74+
MinHtlcMsat: policyBounds.MinHtlcMsat,
75+
MaxHtlcMsat: policyBounds.MaxHtlcMsat,
76+
}, nil
77+
}
78+
79+
// EmptyValue returns a new instance of ChanPolicyBounds.
80+
//
81+
// NOTE: This is part of the Manager interface.
82+
func (b *ChanPolicyBoundsMgr) EmptyValue() Values {
83+
return &ChanPolicyBounds{}
84+
}
85+
86+
// ChanPolicyBounds represents the channel policy bounds rule.
87+
type ChanPolicyBounds struct {
88+
// MinBaseMsat is the minimum base fee in msat that can set for a
89+
// channel.
90+
MinBaseMsat uint64 `json:"min_base_msat"`
91+
92+
// MaxBaseMsat is the maximum base fee in msat that can set for a
93+
// channel.
94+
MaxBaseMsat uint64 `json:"max_base_msat"`
95+
96+
// MinRatePPM is the minimum ppm fee in msat that can be set for a
97+
// channel.
98+
MinRatePPM uint32 `json:"min_rate_ppm"`
99+
100+
// MaxRatePPM is the maximum ppm fee in msat that can be set for a
101+
// channel.
102+
MaxRatePPM uint32 `json:"max_rate_ppm"`
103+
104+
// MinCLTVDelta is the minimum cltv delta that may set for a channel.
105+
MinCLTVDelta uint32 `json:"min_cltv_delta"`
106+
107+
// MaxCLTVDelta is the maximum cltv delta that may set for a channel.
108+
MaxCLTVDelta uint32 `json:"max_cltv_delta"`
109+
110+
// MinHtlcMsat is the minimum htlc size msat that may set for a channel.
111+
MinHtlcMsat uint64 `json:"min_htlc_msat"`
112+
113+
// MaxHtlcMsat is the maximum htlc size in msat that may be set for a
114+
// channel.
115+
MaxHtlcMsat uint64 `json:"max_htlc_msat"`
116+
}
117+
118+
// HandleRequest checks the validity of a request using the ChanPolicyBounds
119+
// rpcmiddleware.RoundTripCheckers.
120+
//
121+
// NOTE: this is part of the Enforcer interface.
122+
func (f *ChanPolicyBounds) HandleRequest(ctx context.Context, uri string,
123+
msg proto.Message) (proto.Message, error) {
124+
125+
checkers := f.checkers()
126+
if checkers == nil {
127+
return nil, nil
128+
}
129+
130+
checker, ok := checkers[uri]
131+
if !ok {
132+
return nil, nil
133+
}
134+
135+
if !checker.HandlesRequest(msg.ProtoReflect().Type()) {
136+
return nil, fmt.Errorf("invalid implementation, checker "+
137+
"for URI %s does not accept request of type %v",
138+
uri, msg.ProtoReflect().Type())
139+
}
140+
141+
return checker.HandleRequest(ctx, msg)
142+
}
143+
144+
// HandleResponse handles and possible alters a response. This is a noop for the
145+
// ChanPolicyBounds rule.
146+
//
147+
// NOTE: this is part of the Enforcer interface.
148+
func (f *ChanPolicyBounds) HandleResponse(_ context.Context, _ string,
149+
_ proto.Message) (proto.Message, error) {
150+
151+
return nil, nil
152+
}
153+
154+
// HandleErrorResponse handles and possible alters an error. This is a noop for
155+
// the ChanPolicyBounds rule.
156+
//
157+
// NOTE: this is part of the Enforcer interface.
158+
func (f *ChanPolicyBounds) HandleErrorResponse(_ context.Context, _ string,
159+
_ error) (error, error) {
160+
161+
return nil, nil
162+
}
163+
164+
// checkers returns a map of URI to rpcmiddleware.RoundTripChecker which define
165+
// how the URI should be handled.
166+
func (f *ChanPolicyBounds) checkers() map[string]mid.RoundTripChecker {
167+
return map[string]mid.RoundTripChecker{
168+
"/lnrpc.Lightning/UpdateChannelPolicy": mid.NewRequestChecker(
169+
&lnrpc.PolicyUpdateRequest{},
170+
&lnrpc.PolicyUpdateResponse{},
171+
func(ctx context.Context,
172+
r *lnrpc.PolicyUpdateRequest) error {
173+
174+
return f.checkPolicyUpdate(ctx, r)
175+
},
176+
),
177+
}
178+
}
179+
180+
// checkPolicyUpdate verifies that the given lnrpc.PolicyUpdateRequest request
181+
// is valid given the ChanPolicyBounds values.
182+
func (f *ChanPolicyBounds) checkPolicyUpdate(_ context.Context,
183+
req *lnrpc.PolicyUpdateRequest) error {
184+
185+
if req.BaseFeeMsat < int64(f.MinBaseMsat) ||
186+
req.BaseFeeMsat > int64(f.MaxBaseMsat) {
187+
188+
return fmt.Errorf("invalid base fee amount")
189+
}
190+
191+
if req.FeeRate == 0 && req.FeeRatePpm == 0 && f.MinRatePPM > 0 {
192+
return fmt.Errorf("invalid fee rate")
193+
}
194+
195+
feeRate := req.FeeRatePpm
196+
if req.FeeRate != 0 {
197+
feeRate = uint32(math.Round(req.FeeRate * 1000000))
198+
}
199+
200+
if feeRate < f.MinRatePPM || feeRate > f.MaxRatePPM {
201+
return fmt.Errorf("invalid fee rate")
202+
}
203+
204+
if req.TimeLockDelta < f.MinCLTVDelta ||
205+
req.TimeLockDelta > f.MaxCLTVDelta {
206+
207+
return fmt.Errorf("invalid cltv delta")
208+
}
209+
210+
if req.MinHtlcMsatSpecified {
211+
if req.MinHtlcMsat < f.MinHtlcMsat {
212+
return fmt.Errorf("invalid min htlc msat amount")
213+
}
214+
}
215+
216+
if req.MaxHtlcMsat > f.MaxHtlcMsat {
217+
return fmt.Errorf("invalid max htlc msat amount")
218+
}
219+
220+
return nil
221+
}
222+
223+
// VerifySane checks that the value of the values is ok given the min and max
224+
// allowed values.
225+
//
226+
// NOTE: this is part of the Values interface.
227+
func (f *ChanPolicyBounds) VerifySane(minVal, maxVal Values) error {
228+
minFB, ok := minVal.(*ChanPolicyBounds)
229+
if !ok {
230+
return fmt.Errorf("min value is not of type ChanPolicyBounds")
231+
}
232+
233+
maxFB, ok := maxVal.(*ChanPolicyBounds)
234+
if !ok {
235+
return fmt.Errorf("max value is not of type ChanPolicyBounds")
236+
}
237+
238+
if !(f.MinBaseMsat >= minFB.MinBaseMsat &&
239+
f.MinBaseMsat <= maxFB.MinBaseMsat) {
240+
241+
return fmt.Errorf("invalid min base fee")
242+
}
243+
244+
if !(f.MaxBaseMsat >= minFB.MaxBaseMsat &&
245+
f.MaxBaseMsat <= maxFB.MaxBaseMsat) {
246+
247+
return fmt.Errorf("invalid max base fee")
248+
}
249+
250+
if !(f.MinRatePPM >= minFB.MinRatePPM &&
251+
f.MinRatePPM <= maxFB.MinRatePPM) {
252+
253+
return fmt.Errorf("invalid min proportional fee")
254+
}
255+
256+
if !(f.MaxRatePPM >= minFB.MaxRatePPM &&
257+
f.MaxRatePPM <= maxFB.MaxRatePPM) {
258+
259+
return fmt.Errorf("invalid max proportional fee")
260+
}
261+
262+
if !(f.MinCLTVDelta >= minFB.MinCLTVDelta &&
263+
f.MinCLTVDelta <= maxFB.MinCLTVDelta) {
264+
265+
return fmt.Errorf("invalid min cltv delta")
266+
}
267+
268+
if !(f.MaxCLTVDelta >= minFB.MaxCLTVDelta &&
269+
f.MaxCLTVDelta <= maxFB.MaxCLTVDelta) {
270+
271+
return fmt.Errorf("invalid max cltv delta")
272+
}
273+
274+
if !(f.MinHtlcMsat >= minFB.MinHtlcMsat &&
275+
f.MinHtlcMsat <= maxFB.MinHtlcMsat) {
276+
277+
return fmt.Errorf("invalid min htlc msat amt")
278+
}
279+
280+
if !(f.MaxHtlcMsat >= minFB.MaxHtlcMsat &&
281+
f.MaxHtlcMsat <= maxFB.MaxHtlcMsat) {
282+
283+
return fmt.Errorf("invalid max htlc msat amt")
284+
}
285+
286+
return nil
287+
}
288+
289+
// ToProto converts the rule Values to the litrpc counterpart.
290+
//
291+
// NOTE: this is part of the Values interface.
292+
func (f *ChanPolicyBounds) ToProto() *litrpc.RuleValue {
293+
return &litrpc.RuleValue{
294+
Value: &litrpc.RuleValue_ChanPolicyBounds{
295+
ChanPolicyBounds: &litrpc.ChannelPolicyBounds{
296+
MinBaseMsat: f.MinBaseMsat,
297+
MaxBaseMsat: f.MaxBaseMsat,
298+
MinRatePpm: f.MinRatePPM,
299+
MaxRatePpm: f.MaxRatePPM,
300+
MinCltvDelta: f.MinCLTVDelta,
301+
MaxCltvDelta: f.MaxCLTVDelta,
302+
MinHtlcMsat: f.MinHtlcMsat,
303+
MaxHtlcMsat: f.MaxHtlcMsat,
304+
},
305+
},
306+
}
307+
}
308+
309+
// RuleName returns the name of the rule that these values are to be used with.
310+
//
311+
// NOTE: this is part of the Values interface.
312+
func (f *ChanPolicyBounds) RuleName() string {
313+
return ChanPolicyBoundsName
314+
}
315+
316+
// PseudoToReal attempts to convert any appropriate pseudo fields in the rule
317+
// Values to their corresponding real values. It uses the passed PrivacyMapDB to
318+
// find the real values. This is a no-op for the ChanPolicyBounds rule.
319+
//
320+
// NOTE: this is part of the Values interface.
321+
func (f *ChanPolicyBounds) PseudoToReal(_ firewalldb.PrivacyMapDB) (Values,
322+
error) {
323+
324+
return f, nil
325+
}
326+
327+
// RealToPseudo converts the rule Values to a new one that uses pseudo keys,
328+
// channel IDs, channel points etc. It returns a map of real to pseudo strings
329+
// that should be persisted. This is a no-op for the ChanPolicyBounds rule.
330+
//
331+
// NOTE: this is part of the Values interface.
332+
func (f *ChanPolicyBounds) RealToPseudo() (Values, map[string]string, error) {
333+
return f, nil, nil
334+
}

0 commit comments

Comments
 (0)