routing: add FetchPayment method to ControlTower

This method is used to fetch a payment and all HTLC attempt that have
been made for that payment. It will both be used to resume inflight
attempts, and to fetch the final outcome of previous attempts.

We also update the the mock control tower to mimic the real control
tower, by letting it track multiple HTLC attempts for a given payment
hash, laying the groundwork for later enabling it for MPP.
This commit is contained in:
Johan T. Halseth 2020-04-01 00:13:23 +02:00
parent 6d9f9c31f4
commit e1f4d89ad9
No known key found for this signature in database
GPG Key ID: 15BAADA29DA20D26
2 changed files with 169 additions and 25 deletions

@ -34,6 +34,10 @@ type ControlTower interface {
// FailAttempt marks the given payment attempt failed. // FailAttempt marks the given payment attempt failed.
FailAttempt(lntypes.Hash, uint64, *channeldb.HTLCFailInfo) error FailAttempt(lntypes.Hash, uint64, *channeldb.HTLCFailInfo) error
// FetchPayment fetches the payment corresponding to the given payment
// hash.
FetchPayment(paymentHash lntypes.Hash) (*channeldb.MPPayment, error)
// Fail transitions a payment into the Failed state, and records the // Fail transitions a payment into the Failed state, and records the
// ultimate reason the payment failed. Note that this should only be // ultimate reason the payment failed. Note that this should only be
// called when all active active attempts are already failed. After // called when all active active attempts are already failed. After
@ -132,6 +136,13 @@ func (p *controlTower) FailAttempt(paymentHash lntypes.Hash,
return p.db.FailAttempt(paymentHash, attemptID, failInfo) return p.db.FailAttempt(paymentHash, attemptID, failInfo)
} }
// FetchPayment fetches the payment corresponding to the given payment hash.
func (p *controlTower) FetchPayment(paymentHash lntypes.Hash) (
*channeldb.MPPayment, error) {
return p.db.FetchPayment(paymentHash)
}
// createSuccessResult creates a success result to send to subscribers. // createSuccessResult creates a success result to send to subscribers.
func createSuccessResult(htlcs []channeldb.HTLCAttempt) *PaymentResult { func createSuccessResult(htlcs []channeldb.HTLCAttempt) *PaymentResult {
// Extract any preimage from the list of HTLCs. // Extract any preimage from the list of HTLCs.

@ -188,9 +188,15 @@ type failArgs struct {
reason channeldb.FailureReason reason channeldb.FailureReason
} }
type testPayment struct {
info channeldb.PaymentCreationInfo
attempts []channeldb.HTLCAttempt
}
type mockControlTower struct { type mockControlTower struct {
inflights map[lntypes.Hash]channeldb.InFlightPayment payments map[lntypes.Hash]*testPayment
successful map[lntypes.Hash]struct{} successful map[lntypes.Hash]struct{}
failed map[lntypes.Hash]channeldb.FailureReason
init chan initArgs init chan initArgs
register chan registerArgs register chan registerArgs
@ -205,8 +211,9 @@ var _ ControlTower = (*mockControlTower)(nil)
func makeMockControlTower() *mockControlTower { func makeMockControlTower() *mockControlTower {
return &mockControlTower{ return &mockControlTower{
inflights: make(map[lntypes.Hash]channeldb.InFlightPayment), payments: make(map[lntypes.Hash]*testPayment),
successful: make(map[lntypes.Hash]struct{}), successful: make(map[lntypes.Hash]struct{}),
failed: make(map[lntypes.Hash]channeldb.FailureReason),
} }
} }
@ -220,18 +227,22 @@ func (m *mockControlTower) InitPayment(phash lntypes.Hash,
m.init <- initArgs{c} m.init <- initArgs{c}
} }
// Don't allow re-init a successful payment.
if _, ok := m.successful[phash]; ok { if _, ok := m.successful[phash]; ok {
return fmt.Errorf("already successful") return channeldb.ErrAlreadyPaid
} }
_, ok := m.inflights[phash] _, failed := m.failed[phash]
if ok { _, ok := m.payments[phash]
return fmt.Errorf("in flight")
// If the payment is known, only allow re-init if failed.
if ok && !failed {
return channeldb.ErrPaymentInFlight
} }
m.inflights[phash] = channeldb.InFlightPayment{ delete(m.failed, phash)
Info: c, m.payments[phash] = &testPayment{
Attempts: make([]channeldb.HTLCAttemptInfo, 0), info: *c,
} }
return nil return nil
@ -247,13 +258,24 @@ func (m *mockControlTower) RegisterAttempt(phash lntypes.Hash,
m.register <- registerArgs{a} m.register <- registerArgs{a}
} }
p, ok := m.inflights[phash] // Cannot register attempts for successful or failed payments.
if !ok { if _, ok := m.successful[phash]; ok {
return fmt.Errorf("not in flight") return channeldb.ErrPaymentAlreadySucceeded
} }
p.Attempts = append(p.Attempts, *a) if _, ok := m.failed[phash]; ok {
m.inflights[phash] = p return channeldb.ErrPaymentAlreadyFailed
}
p, ok := m.payments[phash]
if !ok {
return channeldb.ErrPaymentNotInitiated
}
p.attempts = append(p.attempts, channeldb.HTLCAttempt{
HTLCAttemptInfo: *a,
})
m.payments[phash] = p
return nil return nil
} }
@ -268,9 +290,69 @@ func (m *mockControlTower) SettleAttempt(phash lntypes.Hash,
m.success <- successArgs{settleInfo.Preimage} m.success <- successArgs{settleInfo.Preimage}
} }
delete(m.inflights, phash) // Only allow setting attempts for payments not yet succeeded or
// failed.
if _, ok := m.successful[phash]; ok {
return channeldb.ErrPaymentAlreadySucceeded
}
if _, ok := m.failed[phash]; ok {
return channeldb.ErrPaymentAlreadyFailed
}
p, ok := m.payments[phash]
if !ok {
return channeldb.ErrPaymentNotInitiated
}
// Find the attempt with this pid, and set the settle info.
for i, a := range p.attempts {
if a.AttemptID != pid {
continue
}
p.attempts[i].Settle = settleInfo
// Mark the payment successful on first settled attempt.
m.successful[phash] = struct{}{} m.successful[phash] = struct{}{}
return nil return nil
}
return fmt.Errorf("pid not found")
}
func (m *mockControlTower) FailAttempt(phash lntypes.Hash, pid uint64,
failInfo *channeldb.HTLCFailInfo) error {
m.Lock()
defer m.Unlock()
// Only allow failing attempts for payments not yet succeeded or
// failed.
if _, ok := m.successful[phash]; ok {
return channeldb.ErrPaymentAlreadySucceeded
}
if _, ok := m.failed[phash]; ok {
return channeldb.ErrPaymentAlreadyFailed
}
p, ok := m.payments[phash]
if !ok {
return channeldb.ErrPaymentNotInitiated
}
// Find the attempt with this pid, and set the failure info.
for i, a := range p.attempts {
if a.AttemptID != pid {
continue
}
p.attempts[i].Failure = failInfo
return nil
}
return fmt.Errorf("pid not found")
} }
func (m *mockControlTower) Fail(phash lntypes.Hash, func (m *mockControlTower) Fail(phash lntypes.Hash,
@ -283,10 +365,50 @@ func (m *mockControlTower) Fail(phash lntypes.Hash,
m.fail <- failArgs{reason} m.fail <- failArgs{reason}
} }
delete(m.inflights, phash) // Cannot fail already successful or failed payments.
if _, ok := m.successful[phash]; ok {
return channeldb.ErrPaymentAlreadySucceeded
}
if _, ok := m.failed[phash]; ok {
return channeldb.ErrPaymentAlreadyFailed
}
if _, ok := m.payments[phash]; !ok {
return channeldb.ErrPaymentNotInitiated
}
m.failed[phash] = reason
return nil return nil
} }
func (m *mockControlTower) FetchPayment(phash lntypes.Hash) (
*channeldb.MPPayment, error) {
m.Lock()
defer m.Unlock()
p, ok := m.payments[phash]
if !ok {
return nil, channeldb.ErrPaymentNotInitiated
}
mp := &channeldb.MPPayment{
Info: &p.info,
}
reason, ok := m.failed[phash]
if ok {
mp.FailureReason = &reason
}
// Return a copy of the current attempts.
mp.HTLCs = append(mp.HTLCs, p.attempts...)
return mp, nil
}
func (m *mockControlTower) FetchInFlightPayments() ( func (m *mockControlTower) FetchInFlightPayments() (
[]*channeldb.InFlightPayment, error) { []*channeldb.InFlightPayment, error) {
@ -297,8 +419,25 @@ func (m *mockControlTower) FetchInFlightPayments() (
m.fetchInFlight <- struct{}{} m.fetchInFlight <- struct{}{}
} }
// In flight are all payments not successful or failed.
var fl []*channeldb.InFlightPayment var fl []*channeldb.InFlightPayment
for _, ifl := range m.inflights { for hash, p := range m.payments {
if _, ok := m.successful[hash]; ok {
continue
}
if _, ok := m.failed[hash]; ok {
continue
}
var attempts []channeldb.HTLCAttemptInfo
for _, a := range p.attempts {
attempts = append(attempts, a.HTLCAttemptInfo)
}
ifl := channeldb.InFlightPayment{
Info: &p.info,
Attempts: attempts,
}
fl = append(fl, &ifl) fl = append(fl, &ifl)
} }
@ -310,9 +449,3 @@ func (m *mockControlTower) SubscribePayment(paymentHash lntypes.Hash) (
return false, nil, errors.New("not implemented") return false, nil, errors.New("not implemented")
} }
func (m *mockControlTower) FailAttempt(hash lntypes.Hash, pid uint64,
failInfo *channeldb.HTLCFailInfo) error {
return nil
}