-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathreputation_tracker.go
176 lines (138 loc) · 4.74 KB
/
reputation_tracker.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
package lrc
import (
"errors"
"fmt"
"math"
"time"
"github.com/lightningnetwork/lnd/clock"
)
var (
// ErrResolutionNotFound is returned when we get a resolution for a
// HTLC that is not found in our in flight set.
ErrResolutionNotFound = errors.New("resolved htlc not found")
// ErrDuplicateIndex is returned when an incoming htlc index is
// duplicated.
ErrDuplicateIndex = errors.New("htlc index duplicated")
)
// Compile time check that reputationTracker implements the reputationMonitor
// interface.
var _ reputationMonitor = (*reputationTracker)(nil)
func newReputationTracker(clock clock.Clock, params ManagerParams,
log Logger, startValue *DecayingAverageStart) *reputationTracker {
return &reputationTracker{
revenue: newDecayingAverage(
clock, params.reputationWindow(), startValue,
),
inFlightHTLCs: make(map[int]*InFlightHTLC),
blockTime: float64(params.BlockTime),
resolutionPeriod: params.ResolutionPeriod,
log: log,
}
}
type reputationTracker struct {
// revenue tracks the bi-directional revenue that this channel has
// earned the local node as the incoming edge for HTLC forwards.
revenue *decayingAverage
// inFlightHTLCs provides a map of in-flight HTLCs, keyed by htlc id.
inFlightHTLCs map[int]*InFlightHTLC
// blockTime is the expected time to find a block, surfaced to account
// for simulation scenarios where this isn't 10 minutes.
blockTime float64
// resolutionPeriod is the amount of time that we reasonably expect
// a htlc to resolve in.
resolutionPeriod time.Duration
log Logger
}
func (r *reputationTracker) IncomingReputation() IncomingReputation {
return IncomingReputation{
IncomingRevenue: r.revenue.getValue(),
InFlightRisk: r.inFlightHTLCRisk(),
}
}
// AddInFlight updates the outgoing channel's view to include a new in flight
// HTLC.
func (r *reputationTracker) AddInFlight(htlc *ProposedHTLC,
outgoingDecision ForwardOutcome) error {
inFlightHTLC := &InFlightHTLC{
TimestampAdded: r.revenue.clock.Now(),
ProposedHTLC: htlc,
OutgoingDecision: outgoingDecision,
}
// Sanity check whether the HTLC is already present.
if _, ok := r.inFlightHTLCs[htlc.IncomingIndex]; ok {
return fmt.Errorf("%w: %v", ErrDuplicateIndex,
htlc.IncomingIndex)
}
r.inFlightHTLCs[htlc.IncomingIndex] = inFlightHTLC
return nil
}
// ResolveInFlight removes a htlc from the reputation tracker's state,
// returning an error if it is not found, and updates the link's reputation
// accordingly. It will also return the original in flight htlc when
// successfully removed.
func (r *reputationTracker) ResolveInFlight(htlc *ResolvedHTLC) (*InFlightHTLC,
error) {
inFlight, ok := r.inFlightHTLCs[htlc.IncomingIndex]
if !ok {
return nil, fmt.Errorf("%w: %v(%v) -> %v(%v)", ErrResolutionNotFound,
htlc.IncomingChannel.ToUint64(), htlc.IncomingIndex,
htlc.OutgoingChannel.ToUint64(), htlc.OutgoingIndex)
}
delete(r.inFlightHTLCs, inFlight.IncomingIndex)
effectiveFees := effectiveFees(
r.resolutionPeriod, htlc.TimestampSettled, inFlight,
htlc.Success,
)
r.log.Infof("Adding effective fees to channel: %v: %v",
htlc.IncomingChannel.ToUint64(), effectiveFees)
r.revenue.add(effectiveFees)
return inFlight, nil
}
// inFlightHTLCRisk returns the total outstanding risk of the incoming
// in-flight HTLCs from a specific channel.
func (r *reputationTracker) inFlightHTLCRisk() float64 {
var inFlightRisk float64
for _, htlc := range r.inFlightHTLCs {
// Only endorsed HTLCs count towards our in flight risk.
if htlc.IncomingEndorsed != EndorsementTrue {
continue
}
inFlightRisk += outstandingRisk(
r.blockTime, htlc.ProposedHTLC, r.resolutionPeriod,
)
}
return inFlightRisk
}
func effectiveFees(resolutionPeriod time.Duration, timestampSettled time.Time,
htlc *InFlightHTLC, success bool) float64 {
resolutionTime := timestampSettled.Sub(htlc.TimestampAdded).Seconds()
resolutionSeconds := resolutionPeriod.Seconds()
fee := float64(htlc.ForwardingFee())
opportunityCost := math.Ceil(
(resolutionTime-resolutionSeconds)/resolutionSeconds,
) * fee
switch {
// Successful, endorsed HTLC.
case htlc.IncomingEndorsed == EndorsementTrue && success:
return fee - opportunityCost
// Failed, endorsed HTLC.
case htlc.IncomingEndorsed == EndorsementTrue:
return -1 * opportunityCost
// Successful, unendorsed HTLC.
case success:
if resolutionTime <= resolutionPeriod.Seconds() {
return fee
}
return 0
// Failed, unendorsed HTLC.
default:
return 0
}
}
// outstandingRisk calculates the outstanding risk of in-flight HTLCs.
func outstandingRisk(blockTime float64, htlc *ProposedHTLC,
resolutionPeriod time.Duration) float64 {
return (float64(htlc.ForwardingFee()) *
float64(htlc.CltvExpiryDelta) * blockTime * 60) /
resolutionPeriod.Seconds()
}