Merge pull request #3594 from joostjager/resolver-onion-decode

cnct: decode onion for incoming contest resolver
This commit is contained in:
Olaoluwa Osuntokun authored and GitHub committed 2019-11-12 20:41:21 -08:00
commit 9cc4125ca2
14 files changed
+290 -193

No files matched your search

+19 -11
View File
@@ -161,9 +161,9 @@ func assertResolversEqual(t *testing.T, originalResolver ContractResolver,
t.Fatalf("expected %v, got %v", t.Fatalf("expected %v, got %v",
ogRes.broadcastHeight, diskRes.broadcastHeight) ogRes.broadcastHeight, diskRes.broadcastHeight)
} }
if ogRes.htlcIndex != diskRes.htlcIndex { if ogRes.htlc.HtlcIndex != diskRes.htlc.HtlcIndex {
t.Fatalf("expected %v, got %v", ogRes.htlcIndex, t.Fatalf("expected %v, got %v", ogRes.htlc.HtlcIndex,
diskRes.htlcIndex) diskRes.htlc.HtlcIndex)
} }
} }
@@ -184,9 +184,9 @@ func assertResolversEqual(t *testing.T, originalResolver ContractResolver,
t.Fatalf("expected %v, got %v", t.Fatalf("expected %v, got %v",
ogRes.broadcastHeight, diskRes.broadcastHeight) ogRes.broadcastHeight, diskRes.broadcastHeight)
} }
if ogRes.payHash != diskRes.payHash { if ogRes.htlc.RHash != diskRes.htlc.RHash {
t.Fatalf("expected %v, got %v", ogRes.payHash, t.Fatalf("expected %v, got %v", ogRes.htlc.RHash,
diskRes.payHash) diskRes.htlc.RHash)
} }
} }
@@ -265,7 +265,9 @@ func TestContractInsertionRetrieval(t *testing.T) {
outputIncubating: true, outputIncubating: true,
resolved: true, resolved: true,
broadcastHeight: 102, broadcastHeight: 102,
htlcIndex: 12, htlc: channeldb.HTLC{
HtlcIndex: 12,
},
} }
successResolver := htlcSuccessResolver{ successResolver := htlcSuccessResolver{
htlcResolution: lnwallet.IncomingHtlcResolution{ htlcResolution: lnwallet.IncomingHtlcResolution{
@@ -278,8 +280,10 @@ func TestContractInsertionRetrieval(t *testing.T) {
outputIncubating: true, outputIncubating: true,
resolved: true, resolved: true,
broadcastHeight: 109, broadcastHeight: 109,
payHash: testPreimage, htlc: channeldb.HTLC{
sweepTx: nil, RHash: testPreimage,
},
sweepTx: nil,
} }
resolvers := []ContractResolver{ resolvers := []ContractResolver{
&timeoutResolver, &timeoutResolver,
@@ -395,7 +399,9 @@ func TestContractResolution(t *testing.T) {
outputIncubating: true, outputIncubating: true,
resolved: true, resolved: true,
broadcastHeight: 192, broadcastHeight: 192,
htlcIndex: 9912, htlc: channeldb.HTLC{
HtlcIndex: 9912,
},
} }
// First, we'll insert the resolver into the database and ensure that // First, we'll insert the resolver into the database and ensure that
@@ -454,7 +460,9 @@ func TestContractSwapping(t *testing.T) {
outputIncubating: true, outputIncubating: true,
resolved: true, resolved: true,
broadcastHeight: 102, broadcastHeight: 102,
htlcIndex: 12, htlc: channeldb.HTLC{
HtlcIndex: 12,
},
} }
contestResolver := &htlcOutgoingContestResolver{ contestResolver := &htlcOutgoingContestResolver{
htlcTimeoutResolver: timeoutResolver, htlcTimeoutResolver: timeoutResolver,
+4
View File
@@ -151,6 +151,10 @@ type ChainArbitratorConfig struct {
// NotifyClosedChannel is a function closure that the ChainArbitrator // NotifyClosedChannel is a function closure that the ChainArbitrator
// will use to notify the ChannelNotifier about a newly closed channel. // will use to notify the ChannelNotifier about a newly closed channel.
NotifyClosedChannel func(wire.OutPoint) NotifyClosedChannel func(wire.OutPoint)
// OnionProcessor is used to decode onion payloads for on-chain
// resolution.
OnionProcessor OnionProcessor
} }
// ChainArbitrator is a sub-system that oversees the on-chain resolution of all // ChainArbitrator is a sub-system that oversees the on-chain resolution of all
+30 -101
View File
@@ -501,9 +501,20 @@ func (c *ChannelArbitrator) relaunchResolvers(commitSet *CommitSet) error {
"resolvers", c.cfg.ChanPoint, len(unresolvedContracts)) "resolvers", c.cfg.ChanPoint, len(unresolvedContracts))
for _, resolver := range unresolvedContracts { for _, resolver := range unresolvedContracts {
if err := c.supplementResolver(resolver, htlcMap); err != nil { htlcResolver, ok := resolver.(htlcContractResolver)
return err if !ok {
continue
} }
htlcPoint := htlcResolver.HtlcPoint()
htlc, ok := htlcMap[htlcPoint]
if !ok {
return fmt.Errorf(
"htlc resolver %T unavailable", resolver,
)
}
htlcResolver.Supplement(*htlc)
} }
c.launchResolvers(unresolvedContracts) c.launchResolvers(unresolvedContracts)
@@ -511,89 +522,6 @@ func (c *ChannelArbitrator) relaunchResolvers(commitSet *CommitSet) error {
return nil return nil
} }
// supplementResolver takes a resolver as it is restored from the log and fills
// in missing data from the htlcMap.
func (c *ChannelArbitrator) supplementResolver(resolver ContractResolver,
htlcMap map[wire.OutPoint]*channeldb.HTLC) error {
switch r := resolver.(type) {
case *htlcSuccessResolver:
return c.supplementSuccessResolver(r, htlcMap)
case *htlcIncomingContestResolver:
return c.supplementIncomingContestResolver(r, htlcMap)
case *htlcTimeoutResolver:
return c.supplementTimeoutResolver(r, htlcMap)
case *htlcOutgoingContestResolver:
return c.supplementTimeoutResolver(
&r.htlcTimeoutResolver, htlcMap,
)
}
return nil
}
// supplementSuccessResolver takes a htlcIncomingContestResolver as it is
// restored from the log and fills in missing data from the htlcMap.
func (c *ChannelArbitrator) supplementIncomingContestResolver(
r *htlcIncomingContestResolver,
htlcMap map[wire.OutPoint]*channeldb.HTLC) error {
res := r.htlcResolution
htlcPoint := res.HtlcPoint()
htlc, ok := htlcMap[htlcPoint]
if !ok {
return errors.New(
"htlc for incoming contest resolver unavailable",
)
}
r.htlcAmt = htlc.Amt
r.circuitKey = channeldb.CircuitKey{
ChanID: c.cfg.ShortChanID,
HtlcID: htlc.HtlcIndex,
}
return nil
}
// supplementSuccessResolver takes a htlcSuccessResolver as it is restored from
// the log and fills in missing data from the htlcMap.
func (c *ChannelArbitrator) supplementSuccessResolver(r *htlcSuccessResolver,
htlcMap map[wire.OutPoint]*channeldb.HTLC) error {
res := r.htlcResolution
htlcPoint := res.HtlcPoint()
htlc, ok := htlcMap[htlcPoint]
if !ok {
return errors.New(
"htlc for success resolver unavailable",
)
}
r.htlcAmt = htlc.Amt
return nil
}
// supplementTimeoutResolver takes a htlcSuccessResolver as it is restored from
// the log and fills in missing data from the htlcMap.
func (c *ChannelArbitrator) supplementTimeoutResolver(r *htlcTimeoutResolver,
htlcMap map[wire.OutPoint]*channeldb.HTLC) error {
res := r.htlcResolution
htlcPoint := res.HtlcPoint()
htlc, ok := htlcMap[htlcPoint]
if !ok {
return errors.New(
"htlc for timeout resolver unavailable",
)
}
r.htlcAmt = htlc.Amt
return nil
}
// Report returns htlc reports for the active resolvers. // Report returns htlc reports for the active resolvers.
func (c *ChannelArbitrator) Report() []*ContractReport { func (c *ChannelArbitrator) Report() []*ContractReport {
c.activeResolversLock.RLock() c.activeResolversLock.RLock()
@@ -1224,8 +1152,10 @@ func (c *ChannelArbitrator) checkCommitChainActions(height uint32,
// * race condition if adding and we broadcast, etc // * race condition if adding and we broadcast, etc
// * or would make each instance sync? // * or would make each instance sync?
log.Debugf("ChannelArbitrator(%v): checking chain actions at "+ log.Debugf("ChannelArbitrator(%v): checking commit chain actions at "+
"height=%v", c.cfg.ChanPoint, height) "height=%v, in_htlc_count=%v, out_htlc_count=%v",
c.cfg.ChanPoint, height,
len(htlcs.incomingHTLCs), len(htlcs.outgoingHTLCs))
actionMap := make(ChainActionMap) actionMap := make(ChainActionMap)
@@ -1719,6 +1649,8 @@ func (c *ChannelArbitrator) prepContractResolutions(
// claim the HTLC (second-level or directly), then add the pre // claim the HTLC (second-level or directly), then add the pre
case HtlcClaimAction: case HtlcClaimAction:
for _, htlc := range htlcs { for _, htlc := range htlcs {
htlc := htlc
htlcOp := wire.OutPoint{ htlcOp := wire.OutPoint{
Hash: commitHash, Hash: commitHash,
Index: uint32(htlc.OutputIndex), Index: uint32(htlc.OutputIndex),
@@ -1734,8 +1666,7 @@ func (c *ChannelArbitrator) prepContractResolutions(
} }
resolver := newSuccessResolver( resolver := newSuccessResolver(
resolution, height, resolution, height, htlc, resolverCfg,
htlc.RHash, htlc.Amt, resolverCfg,
) )
htlcResolvers = append(htlcResolvers, resolver) htlcResolvers = append(htlcResolvers, resolver)
} }
@@ -1745,6 +1676,8 @@ func (c *ChannelArbitrator) prepContractResolutions(
// backwards. // backwards.
case HtlcTimeoutAction: case HtlcTimeoutAction:
for _, htlc := range htlcs { for _, htlc := range htlcs {
htlc := htlc
htlcOp := wire.OutPoint{ htlcOp := wire.OutPoint{
Hash: commitHash, Hash: commitHash,
Index: uint32(htlc.OutputIndex), Index: uint32(htlc.OutputIndex),
@@ -1758,8 +1691,7 @@ func (c *ChannelArbitrator) prepContractResolutions(
} }
resolver := newTimeoutResolver( resolver := newTimeoutResolver(
resolution, height, htlc.HtlcIndex, resolution, height, htlc, resolverCfg,
htlc.Amt, resolverCfg,
) )
htlcResolvers = append(htlcResolvers, resolver) htlcResolvers = append(htlcResolvers, resolver)
} }
@@ -1769,6 +1701,8 @@ func (c *ChannelArbitrator) prepContractResolutions(
// learn of the pre-image, or let the remote party time out. // learn of the pre-image, or let the remote party time out.
case HtlcIncomingWatchAction: case HtlcIncomingWatchAction:
for _, htlc := range htlcs { for _, htlc := range htlcs {
htlc := htlc
htlcOp := wire.OutPoint{ htlcOp := wire.OutPoint{
Hash: commitHash, Hash: commitHash,
Index: uint32(htlc.OutputIndex), Index: uint32(htlc.OutputIndex),
@@ -1785,15 +1719,9 @@ func (c *ChannelArbitrator) prepContractResolutions(
continue continue
} }
circuitKey := channeldb.CircuitKey{
HtlcID: htlc.HtlcIndex,
ChanID: c.cfg.ShortChanID,
}
resolver := newIncomingContestResolver( resolver := newIncomingContestResolver(
htlc.RefundTimeout, circuitKey, resolution, height, htlc,
resolution, height, htlc.RHash, resolverCfg,
htlc.Amt, resolverCfg,
) )
htlcResolvers = append(htlcResolvers, resolver) htlcResolvers = append(htlcResolvers, resolver)
} }
@@ -1803,6 +1731,8 @@ func (c *ChannelArbitrator) prepContractResolutions(
// backwards), or just timeout. // backwards), or just timeout.
case HtlcOutgoingWatchAction: case HtlcOutgoingWatchAction:
for _, htlc := range htlcs { for _, htlc := range htlcs {
htlc := htlc
htlcOp := wire.OutPoint{ htlcOp := wire.OutPoint{
Hash: commitHash, Hash: commitHash,
Index: uint32(htlc.OutputIndex), Index: uint32(htlc.OutputIndex),
@@ -1817,8 +1747,7 @@ func (c *ChannelArbitrator) prepContractResolutions(
} }
resolver := newOutgoingContestResolver( resolver := newOutgoingContestResolver(
resolution, height, htlc.HtlcIndex, resolution, height, htlc, resolverCfg,
htlc.Amt, resolverCfg,
) )
htlcResolvers = append(htlcResolvers, resolver) htlcResolvers = append(htlcResolvers, resolver)
} }
+4 -3
View File
@@ -308,6 +308,7 @@ func createTestChannelArbitrator(t *testing.T, log ArbitratorLog) (*chanArbTestC
incubateChan <- struct{}{} incubateChan <- struct{}{}
return nil return nil
}, },
OnionProcessor: &mockOnionProcessor{},
} }
// We'll use the resolvedChan to synchronize on call to // We'll use the resolvedChan to synchronize on call to
@@ -858,10 +859,10 @@ func TestChannelArbitratorLocalForceClosePendingHtlc(t *testing.T) {
resolver) resolver)
} }
// The resolver should have its htlcAmt field populated as it. // The resolver should have its htlc amt field populated as it.
if int64(outgoingResolver.htlcAmt) != int64(htlcAmt) { if int64(outgoingResolver.htlc.Amt) != int64(htlcAmt) {
t.Fatalf("wrong htlc amount: expected %v, got %v,", t.Fatalf("wrong htlc amount: expected %v, got %v,",
htlcAmt, int64(outgoingResolver.htlcAmt)) htlcAmt, int64(outgoingResolver.htlc.Amt))
} }
// htlcOutgoingContestResolver is now active and waiting for the HTLC to // htlcOutgoingContestResolver is now active and waiting for the HTLC to
+15
View File
@@ -4,6 +4,9 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"io" "io"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
) )
var ( var (
@@ -51,6 +54,18 @@ type ContractResolver interface {
Stop() Stop()
} }
// htlcContractResolver is the required interface for htlc resolvers.
type htlcContractResolver interface {
ContractResolver
// HtlcPoint returns the htlc's outpoint on the commitment tx.
HtlcPoint() wire.OutPoint
// Supplement adds additional information to the resolver that is
// required before Resolve() is called.
Supplement(htlc channeldb.HTLC)
}
// reportingContractResolver is a ContractResolver that also exposes a report on // reportingContractResolver is a ContractResolver that also exposes a report on
// the resolution state of the contract. // the resolution state of the contract.
type reportingContractResolver interface { type reportingContractResolver interface {
+57 -19
View File
@@ -1,16 +1,17 @@
package contractcourt package contractcourt
import ( import (
"bytes"
"encoding/binary" "encoding/binary"
"errors" "errors"
"io" "io"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/invoices" "github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
) )
// htlcIncomingContestResolver is a ContractResolver that's able to resolve an // htlcIncomingContestResolver is a ContractResolver that's able to resolve an
@@ -27,28 +28,22 @@ type htlcIncomingContestResolver struct {
// successfully. // successfully.
htlcExpiry uint32 htlcExpiry uint32
// circuitKey describes the incoming htlc that is being resolved.
circuitKey channeldb.CircuitKey
// htlcSuccessResolver is the inner resolver that may be utilized if we // htlcSuccessResolver is the inner resolver that may be utilized if we
// learn of the preimage. // learn of the preimage.
htlcSuccessResolver htlcSuccessResolver
} }
// newIncomingContestResolver instantiates a new incoming htlc contest resolver. // newIncomingContestResolver instantiates a new incoming htlc contest resolver.
func newIncomingContestResolver(htlcExpiry uint32, func newIncomingContestResolver(
circuitKey channeldb.CircuitKey, res lnwallet.IncomingHtlcResolution, res lnwallet.IncomingHtlcResolution, broadcastHeight uint32,
broadcastHeight uint32, payHash lntypes.Hash, htlc channeldb.HTLC, resCfg ResolverConfig) *htlcIncomingContestResolver {
htlcAmt lnwire.MilliSatoshi,
resCfg ResolverConfig) *htlcIncomingContestResolver {
success := newSuccessResolver( success := newSuccessResolver(
res, broadcastHeight, payHash, htlcAmt, resCfg, res, broadcastHeight, htlc, resCfg,
) )
return &htlcIncomingContestResolver{ return &htlcIncomingContestResolver{
htlcExpiry: htlcExpiry, htlcExpiry: htlc.RefundTimeout,
circuitKey: circuitKey,
htlcSuccessResolver: *success, htlcSuccessResolver: *success,
} }
} }
@@ -72,6 +67,22 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
return nil, nil return nil, nil
} }
// First try to parse the payload. If that fails, we can stop resolution
// now.
payload, err := h.decodePayload()
if err != nil {
log.Debugf("ChannelArbitrator(%v): cannot decode payload of "+
"htlc %v", h.ChanPoint, h.HtlcPoint())
// If we've locked in an htlc with an invalid payload on our
// commitment tx, we don't need to resolve it. The other party
// will time it out and get their funds back. This situation can
// present itself when we crash before processRemoteAdds in the
// link has ran.
h.resolved = true
return nil, nil
}
// Register for block epochs. After registration, the current height // Register for block epochs. After registration, the current height
// will be sent on the channel immediately. // will be sent on the channel immediately.
blockEpochs, err := h.Notifier.RegisterBlockEpochNtfn(nil) blockEpochs, err := h.Notifier.RegisterBlockEpochNtfn(nil)
@@ -119,7 +130,7 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
applyPreimage := func(preimage lntypes.Preimage) error { applyPreimage := func(preimage lntypes.Preimage) error {
// Sanity check to see if this preimage matches our htlc. At // Sanity check to see if this preimage matches our htlc. At
// this point it should never happen that it does not match. // this point it should never happen that it does not match.
if !preimage.Matches(h.payHash) { if !preimage.Matches(h.htlc.RHash) {
return errors.New("preimage does not match hash") return errors.New("preimage does not match hash")
} }
@@ -185,9 +196,14 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
// on-chain. If this HTLC indeed pays to an existing invoice, the // on-chain. If this HTLC indeed pays to an existing invoice, the
// invoice registry will tell us what to do with the HTLC. This is // invoice registry will tell us what to do with the HTLC. This is
// identical to HTLC resolution in the link. // identical to HTLC resolution in the link.
circuitKey := channeldb.CircuitKey{
ChanID: h.ShortChanID,
HtlcID: h.htlc.HtlcIndex,
}
event, err := h.Registry.NotifyExitHopHtlc( event, err := h.Registry.NotifyExitHopHtlc(
h.payHash, h.htlcAmt, h.htlcExpiry, currentHeight, h.htlc.RHash, h.htlc.Amt, h.htlcExpiry, currentHeight,
h.circuitKey, hodlChan, nil, circuitKey, hodlChan, payload,
) )
switch err { switch err {
case channeldb.ErrInvoiceNotFound: case channeldb.ErrInvoiceNotFound:
@@ -204,7 +220,7 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
// With the epochs and preimage subscriptions initialized, we'll query // With the epochs and preimage subscriptions initialized, we'll query
// to see if we already know the preimage. // to see if we already know the preimage.
preimage, ok := h.PreimageDB.LookupPreimage(h.payHash) preimage, ok := h.PreimageDB.LookupPreimage(h.htlc.RHash)
if ok { if ok {
// If we do, then this means we can claim the HTLC! However, // If we do, then this means we can claim the HTLC! However,
// we don't know how to ourselves, so we'll return our inner // we don't know how to ourselves, so we'll return our inner
@@ -222,7 +238,7 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
case preimage := <-preimageSubscription.WitnessUpdates: case preimage := <-preimageSubscription.WitnessUpdates:
// We receive all new preimages, so we need to ignore // We receive all new preimages, so we need to ignore
// all except the preimage we are waiting for. // all except the preimage we are waiting for.
if !preimage.Matches(h.payHash) { if !preimage.Matches(h.htlc.RHash) {
continue continue
} }
@@ -268,7 +284,7 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
func (h *htlcIncomingContestResolver) report() *ContractReport { func (h *htlcIncomingContestResolver) report() *ContractReport {
// No locking needed as these values are read-only. // No locking needed as these values are read-only.
finalAmt := h.htlcAmt.ToSatoshis() finalAmt := h.htlc.Amt.ToSatoshis()
if h.htlcResolution.SignedSuccessTx != nil { if h.htlcResolution.SignedSuccessTx != nil {
finalAmt = btcutil.Amount( finalAmt = btcutil.Amount(
h.htlcResolution.SignedSuccessTx.TxOut[0].Value, h.htlcResolution.SignedSuccessTx.TxOut[0].Value,
@@ -338,6 +354,28 @@ func newIncomingContestResolverFromReader(r io.Reader, resCfg ResolverConfig) (
return h, nil return h, nil
} }
// Supplement adds additional information to the resolver that is required
// before Resolve() is called.
//
// NOTE: Part of the htlcContractResolver interface.
func (h *htlcIncomingContestResolver) Supplement(htlc channeldb.HTLC) {
h.htlc = htlc
}
// decodePayload (re)decodes the hop payload of a received htlc.
func (h *htlcIncomingContestResolver) decodePayload() (*hop.Payload, error) {
onionReader := bytes.NewReader(h.htlc.OnionBlob)
iterator, err := h.OnionProcessor.ReconstructHopIterator(
onionReader, h.htlc.RHash[:],
)
if err != nil {
return nil, err
}
return iterator.HopPayload()
}
// A compile time assertion to ensure htlcIncomingContestResolver meets the // A compile time assertion to ensure htlcIncomingContestResolver meets the
// ContractResolver interface. // ContractResolver interface.
var _ ContractResolver = (*htlcIncomingContestResolver)(nil) var _ htlcContractResolver = (*htlcIncomingContestResolver)(nil)
+58 -16
View File
@@ -2,9 +2,12 @@ package contractcourt
import ( import (
"bytes" "bytes"
"io"
"io/ioutil"
"testing" "testing"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/invoices" "github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
@@ -21,6 +24,7 @@ var (
testResPreimage = lntypes.Preimage{1, 2, 3} testResPreimage = lntypes.Preimage{1, 2, 3}
testResHash = testResPreimage.Hash() testResHash = testResPreimage.Hash()
testResCircuitKey = channeldb.CircuitKey{} testResCircuitKey = channeldb.CircuitKey{}
testOnionBlob = []byte{4, 5, 6}
) )
// TestHtlcIncomingResolverFwdPreimageKnown tests resolution of a forwarded htlc // TestHtlcIncomingResolverFwdPreimageKnown tests resolution of a forwarded htlc
@@ -107,6 +111,12 @@ func TestHtlcIncomingResolverExitSettle(t *testing.T) {
} }
ctx.waitForResult(true) ctx.waitForResult(true)
if !bytes.Equal(
ctx.onionProcessor.offeredOnionBlob, testOnionBlob,
) {
t.Fatal("unexpected onion blob")
}
} }
// TestHtlcIncomingResolverExitCancel tests resolution of an exit hop htlc for // TestHtlcIncomingResolverExitCancel tests resolution of an exit hop htlc for
@@ -168,14 +178,39 @@ func TestHtlcIncomingResolverExitCancelHodl(t *testing.T) {
ctx.waitForResult(false) ctx.waitForResult(false)
} }
type mockHopIterator struct {
hop.Iterator
}
func (h *mockHopIterator) HopPayload() (*hop.Payload, error) {
return nil, nil
}
type mockOnionProcessor struct {
offeredOnionBlob []byte
}
func (o *mockOnionProcessor) ReconstructHopIterator(r io.Reader, rHash []byte) (
hop.Iterator, error) {
data, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
o.offeredOnionBlob = data
return &mockHopIterator{}, nil
}
type incomingResolverTestContext struct { type incomingResolverTestContext struct {
registry *mockRegistry registry *mockRegistry
witnessBeacon *mockWitnessBeacon witnessBeacon *mockWitnessBeacon
resolver *htlcIncomingContestResolver resolver *htlcIncomingContestResolver
notifier *mockNotifier notifier *mockNotifier
resolveErr chan error onionProcessor *mockOnionProcessor
nextResolver ContractResolver resolveErr chan error
t *testing.T nextResolver ContractResolver
t *testing.T
} }
func newIncomingResolverTestContext(t *testing.T) *incomingResolverTestContext { func newIncomingResolverTestContext(t *testing.T) *incomingResolverTestContext {
@@ -189,13 +224,16 @@ func newIncomingResolverTestContext(t *testing.T) *incomingResolverTestContext {
notifyChan: make(chan notifyExitHopData, 1), notifyChan: make(chan notifyExitHopData, 1),
} }
onionProcessor := &mockOnionProcessor{}
checkPointChan := make(chan struct{}, 1) checkPointChan := make(chan struct{}, 1)
chainCfg := ChannelArbitratorConfig{ chainCfg := ChannelArbitratorConfig{
ChainArbitratorConfig: ChainArbitratorConfig{ ChainArbitratorConfig: ChainArbitratorConfig{
Notifier: notifier, Notifier: notifier,
PreimageDB: witnessBeacon, PreimageDB: witnessBeacon,
Registry: registry, Registry: registry,
OnionProcessor: onionProcessor,
}, },
} }
@@ -210,17 +248,21 @@ func newIncomingResolverTestContext(t *testing.T) *incomingResolverTestContext {
htlcSuccessResolver: htlcSuccessResolver{ htlcSuccessResolver: htlcSuccessResolver{
contractResolverKit: *newContractResolverKit(cfg), contractResolverKit: *newContractResolverKit(cfg),
htlcResolution: lnwallet.IncomingHtlcResolution{}, htlcResolution: lnwallet.IncomingHtlcResolution{},
payHash: testResHash, htlc: channeldb.HTLC{
RHash: testResHash,
OnionBlob: testOnionBlob,
},
}, },
htlcExpiry: testHtlcExpiry, htlcExpiry: testHtlcExpiry,
} }
return &incomingResolverTestContext{ return &incomingResolverTestContext{
registry: registry, registry: registry,
witnessBeacon: witnessBeacon, witnessBeacon: witnessBeacon,
resolver: resolver, resolver: resolver,
notifier: notifier, notifier: notifier,
t: t, onionProcessor: onionProcessor,
t: t,
} }
} }
@@ -5,8 +5,8 @@ import (
"io" "io"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
) )
// htlcOutgoingContestResolver is a ContractResolver that's able to resolve an // htlcOutgoingContestResolver is a ContractResolver that's able to resolve an
@@ -23,11 +23,11 @@ type htlcOutgoingContestResolver struct {
// newOutgoingContestResolver instantiates a new outgoing contested htlc // newOutgoingContestResolver instantiates a new outgoing contested htlc
// resolver. // resolver.
func newOutgoingContestResolver(res lnwallet.OutgoingHtlcResolution, func newOutgoingContestResolver(res lnwallet.OutgoingHtlcResolution,
broadcastHeight uint32, htlcIndex uint64, htlcAmt lnwire.MilliSatoshi, broadcastHeight uint32, htlc channeldb.HTLC,
resCfg ResolverConfig) *htlcOutgoingContestResolver { resCfg ResolverConfig) *htlcOutgoingContestResolver {
timeout := newTimeoutResolver( timeout := newTimeoutResolver(
res, broadcastHeight, htlcIndex, htlcAmt, resCfg, res, broadcastHeight, htlc, resCfg,
) )
return &htlcOutgoingContestResolver{ return &htlcOutgoingContestResolver{
@@ -157,7 +157,7 @@ func (h *htlcOutgoingContestResolver) Resolve() (ContractResolver, error) {
func (h *htlcOutgoingContestResolver) report() *ContractReport { func (h *htlcOutgoingContestResolver) report() *ContractReport {
// No locking needed as these values are read-only. // No locking needed as these values are read-only.
finalAmt := h.htlcAmt.ToSatoshis() finalAmt := h.htlc.Amt.ToSatoshis()
if h.htlcResolution.SignedTimeoutTx != nil { if h.htlcResolution.SignedTimeoutTx != nil {
finalAmt = btcutil.Amount( finalAmt = btcutil.Amount(
h.htlcResolution.SignedTimeoutTx.TxOut[0].Value, h.htlcResolution.SignedTimeoutTx.TxOut[0].Value,
@@ -215,4 +215,4 @@ func newOutgoingContestResolverFromReader(r io.Reader, resCfg ResolverConfig) (
// A compile time assertion to ensure htlcOutgoingContestResolver meets the // A compile time assertion to ensure htlcOutgoingContestResolver meets the
// ContractResolver interface. // ContractResolver interface.
var _ ContractResolver = (*htlcOutgoingContestResolver)(nil) var _ htlcContractResolver = (*htlcOutgoingContestResolver)(nil)
@@ -6,6 +6,7 @@ import (
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
@@ -98,6 +99,8 @@ func newOutgoingResolverTestContext(t *testing.T) *outgoingResolverTestContext {
preimageDB := newMockWitnessBeacon() preimageDB := newMockWitnessBeacon()
onionProcessor := &mockOnionProcessor{}
chainCfg := ChannelArbitratorConfig{ chainCfg := ChannelArbitratorConfig{
ChainArbitratorConfig: ChainArbitratorConfig{ ChainArbitratorConfig: ChainArbitratorConfig{
Notifier: notifier, Notifier: notifier,
@@ -112,6 +115,7 @@ func newOutgoingResolverTestContext(t *testing.T) *outgoingResolverTestContext {
resolutionChan <- msgs[0] resolutionChan <- msgs[0]
return nil return nil
}, },
OnionProcessor: onionProcessor,
}, },
} }
@@ -134,6 +138,10 @@ func newOutgoingResolverTestContext(t *testing.T) *outgoingResolverTestContext {
htlcTimeoutResolver: htlcTimeoutResolver{ htlcTimeoutResolver: htlcTimeoutResolver{
contractResolverKit: *newContractResolverKit(cfg), contractResolverKit: *newContractResolverKit(cfg),
htlcResolution: outgoingRes, htlcResolution: outgoingRes,
htlc: channeldb.HTLC{
RHash: testResHash,
OnionBlob: testOnionBlob,
},
}, },
} }
+30 -22
View File
@@ -6,10 +6,9 @@ import (
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/sweep" "github.com/lightningnetwork/lnd/sweep"
) )
@@ -38,9 +37,6 @@ type htlcSuccessResolver struct {
// historical queries to the chain for spends/confirmations. // historical queries to the chain for spends/confirmations.
broadcastHeight uint32 broadcastHeight uint32
// payHash is the payment hash of the original HTLC extended to us.
payHash lntypes.Hash
// sweepTx will be non-nil if we've already crafted a transaction to // sweepTx will be non-nil if we've already crafted a transaction to
// sweep a direct HTLC output. This is only a concern if we're sweeping // sweep a direct HTLC output. This is only a concern if we're sweeping
// from the commitment transaction of the remote party. // from the commitment transaction of the remote party.
@@ -48,25 +44,22 @@ type htlcSuccessResolver struct {
// TODO(roasbeef): send off to utxobundler // TODO(roasbeef): send off to utxobundler
sweepTx *wire.MsgTx sweepTx *wire.MsgTx
// htlcAmt is the original amount of the htlc, not taking into // htlc contains information on the htlc that we are resolving on-chain.
// account any fees that may have to be paid if it goes on chain. htlc channeldb.HTLC
htlcAmt lnwire.MilliSatoshi
contractResolverKit contractResolverKit
} }
// newSuccessResolver instanties a new htlc success resolver. // newSuccessResolver instanties a new htlc success resolver.
func newSuccessResolver(res lnwallet.IncomingHtlcResolution, func newSuccessResolver(res lnwallet.IncomingHtlcResolution,
broadcastHeight uint32, payHash lntypes.Hash, broadcastHeight uint32, htlc channeldb.HTLC,
htlcAmt lnwire.MilliSatoshi,
resCfg ResolverConfig) *htlcSuccessResolver { resCfg ResolverConfig) *htlcSuccessResolver {
return &htlcSuccessResolver{ return &htlcSuccessResolver{
contractResolverKit: *newContractResolverKit(resCfg), contractResolverKit: *newContractResolverKit(resCfg),
htlcResolution: res, htlcResolution: res,
broadcastHeight: broadcastHeight, broadcastHeight: broadcastHeight,
payHash: payHash, htlc: htlc,
htlcAmt: htlcAmt,
} }
} }
@@ -114,7 +107,7 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
if h.sweepTx == nil { if h.sweepTx == nil {
log.Infof("%T(%x): crafting sweep tx for "+ log.Infof("%T(%x): crafting sweep tx for "+
"incoming+remote htlc confirmed", h, "incoming+remote htlc confirmed", h,
h.payHash[:]) h.htlc.RHash[:])
// Before we can craft out sweeping transaction, we // Before we can craft out sweeping transaction, we
// need to create an input which contains all the items // need to create an input which contains all the items
@@ -148,7 +141,7 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
} }
log.Infof("%T(%x): crafted sweep tx=%v", h, log.Infof("%T(%x): crafted sweep tx=%v", h,
h.payHash[:], spew.Sdump(h.sweepTx)) h.htlc.RHash[:], spew.Sdump(h.sweepTx))
// With the sweep transaction signed, we'll now // With the sweep transaction signed, we'll now
// Checkpoint our state. // Checkpoint our state.
@@ -164,7 +157,7 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
err := h.PublishTx(h.sweepTx) err := h.PublishTx(h.sweepTx)
if err != nil { if err != nil {
log.Infof("%T(%x): unable to publish tx: %v", log.Infof("%T(%x): unable to publish tx: %v",
h, h.payHash[:], err) h, h.htlc.RHash[:], err)
return nil, err return nil, err
} }
@@ -180,7 +173,7 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
} }
log.Infof("%T(%x): waiting for sweep tx (txid=%v) to be "+ log.Infof("%T(%x): waiting for sweep tx (txid=%v) to be "+
"confirmed", h, h.payHash[:], sweepTXID) "confirmed", h, h.htlc.RHash[:], sweepTXID)
select { select {
case _, ok := <-confNtfn.Confirmed: case _, ok := <-confNtfn.Confirmed:
@@ -199,7 +192,7 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
} }
log.Infof("%T(%x): broadcasting second-layer transition tx: %v", log.Infof("%T(%x): broadcasting second-layer transition tx: %v",
h, h.payHash[:], spew.Sdump(h.htlcResolution.SignedSuccessTx)) h, h.htlc.RHash[:], spew.Sdump(h.htlcResolution.SignedSuccessTx))
// We'll now broadcast the second layer transaction so we can kick off // We'll now broadcast the second layer transaction so we can kick off
// the claiming process. // the claiming process.
@@ -215,7 +208,7 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
// done so. // done so.
if !h.outputIncubating { if !h.outputIncubating {
log.Infof("%T(%x): incubating incoming htlc output", log.Infof("%T(%x): incubating incoming htlc output",
h, h.payHash[:]) h, h.htlc.RHash[:])
err := h.IncubateOutputs( err := h.IncubateOutputs(
h.ChanPoint, nil, nil, &h.htlcResolution, h.ChanPoint, nil, nil, &h.htlcResolution,
@@ -245,7 +238,7 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
} }
log.Infof("%T(%x): waiting for second-level HTLC output to be spent "+ log.Infof("%T(%x): waiting for second-level HTLC output to be spent "+
"after csv_delay=%v", h, h.payHash[:], h.htlcResolution.CsvDelay) "after csv_delay=%v", h, h.htlc.RHash[:], h.htlcResolution.CsvDelay)
select { select {
case _, ok := <-spendNtfn.Spend: case _, ok := <-spendNtfn.Spend:
@@ -298,7 +291,7 @@ func (h *htlcSuccessResolver) Encode(w io.Writer) error {
if err := binary.Write(w, endian, h.broadcastHeight); err != nil { if err := binary.Write(w, endian, h.broadcastHeight); err != nil {
return err return err
} }
if _, err := w.Write(h.payHash[:]); err != nil { if _, err := w.Write(h.htlc.RHash[:]); err != nil {
return err return err
} }
@@ -331,13 +324,28 @@ func newSuccessResolverFromReader(r io.Reader, resCfg ResolverConfig) (
if err := binary.Read(r, endian, &h.broadcastHeight); err != nil { if err := binary.Read(r, endian, &h.broadcastHeight); err != nil {
return nil, err return nil, err
} }
if _, err := io.ReadFull(r, h.payHash[:]); err != nil { if _, err := io.ReadFull(r, h.htlc.RHash[:]); err != nil {
return nil, err return nil, err
} }
return h, nil return h, nil
} }
// Supplement adds additional information to the resolver that is required
// before Resolve() is called.
//
// NOTE: Part of the htlcContractResolver interface.
func (h *htlcSuccessResolver) Supplement(htlc channeldb.HTLC) {
h.htlc = htlc
}
// HtlcPoint returns the htlc's outpoint on the commitment tx.
//
// NOTE: Part of the htlcContractResolver interface.
func (h *htlcSuccessResolver) HtlcPoint() wire.OutPoint {
return h.htlcResolution.HtlcPoint()
}
// A compile time assertion to ensure htlcSuccessResolver meets the // A compile time assertion to ensure htlcSuccessResolver meets the
// ContractResolver interface. // ContractResolver interface.
var _ ContractResolver = (*htlcSuccessResolver)(nil) var _ htlcContractResolver = (*htlcSuccessResolver)(nil)
+25 -16
View File
@@ -8,6 +8,7 @@ import (
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
@@ -40,29 +41,22 @@ type htlcTimeoutResolver struct {
// TODO(roasbeef): wrap above into definite resolution embedding? // TODO(roasbeef): wrap above into definite resolution embedding?
broadcastHeight uint32 broadcastHeight uint32
// htlcIndex is the index of this HTLC within the trace of the // htlc contains information on the htlc that we are resolving on-chain.
// additional commitment state machine. htlc channeldb.HTLC
htlcIndex uint64
// htlcAmt is the original amount of the htlc, not taking into
// account any fees that may have to be paid if it goes on chain.
htlcAmt lnwire.MilliSatoshi
contractResolverKit contractResolverKit
} }
// newTimeoutResolver instantiates a new timeout htlc resolver. // newTimeoutResolver instantiates a new timeout htlc resolver.
func newTimeoutResolver(res lnwallet.OutgoingHtlcResolution, func newTimeoutResolver(res lnwallet.OutgoingHtlcResolution,
broadcastHeight uint32, htlcIndex uint64, broadcastHeight uint32, htlc channeldb.HTLC,
htlcAmt lnwire.MilliSatoshi,
resCfg ResolverConfig) *htlcTimeoutResolver { resCfg ResolverConfig) *htlcTimeoutResolver {
return &htlcTimeoutResolver{ return &htlcTimeoutResolver{
contractResolverKit: *newContractResolverKit(resCfg), contractResolverKit: *newContractResolverKit(resCfg),
htlcResolution: res, htlcResolution: res,
broadcastHeight: broadcastHeight, broadcastHeight: broadcastHeight,
htlcIndex: htlcIndex, htlc: htlc,
htlcAmt: htlcAmt,
} }
} }
@@ -157,7 +151,7 @@ func (h *htlcTimeoutResolver) claimCleanUp(
// resolved, then exit. // resolved, then exit.
if err := h.DeliverResolutionMsg(ResolutionMsg{ if err := h.DeliverResolutionMsg(ResolutionMsg{
SourceChan: h.ShortChanID, SourceChan: h.ShortChanID,
HtlcIndex: h.htlcIndex, HtlcIndex: h.htlc.HtlcIndex,
PreImage: &pre, PreImage: &pre,
}); err != nil { }); err != nil {
return nil, err return nil, err
@@ -352,7 +346,7 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) {
failureMsg := &lnwire.FailPermanentChannelFailure{} failureMsg := &lnwire.FailPermanentChannelFailure{}
if err := h.DeliverResolutionMsg(ResolutionMsg{ if err := h.DeliverResolutionMsg(ResolutionMsg{
SourceChan: h.ShortChanID, SourceChan: h.ShortChanID,
HtlcIndex: h.htlcIndex, HtlcIndex: h.htlc.HtlcIndex,
Failure: failureMsg, Failure: failureMsg,
}); err != nil { }); err != nil {
return nil, err return nil, err
@@ -414,7 +408,7 @@ func (h *htlcTimeoutResolver) Encode(w io.Writer) error {
return err return err
} }
if err := binary.Write(w, endian, h.htlcIndex); err != nil { if err := binary.Write(w, endian, h.htlc.HtlcIndex); err != nil {
return err return err
} }
@@ -449,13 +443,28 @@ func newTimeoutResolverFromReader(r io.Reader, resCfg ResolverConfig) (
return nil, err return nil, err
} }
if err := binary.Read(r, endian, &h.htlcIndex); err != nil { if err := binary.Read(r, endian, &h.htlc.HtlcIndex); err != nil {
return nil, err return nil, err
} }
return h, nil return h, nil
} }
// Supplement adds additional information to the resolver that is required
// before Resolve() is called.
//
// NOTE: Part of the htlcContractResolver interface.
func (h *htlcTimeoutResolver) Supplement(htlc channeldb.HTLC) {
h.htlc = htlc
}
// HtlcPoint returns the htlc's outpoint on the commitment tx.
//
// NOTE: Part of the htlcContractResolver interface.
func (h *htlcTimeoutResolver) HtlcPoint() wire.OutPoint {
return h.htlcResolution.HtlcPoint()
}
// A compile time assertion to ensure htlcTimeoutResolver meets the // A compile time assertion to ensure htlcTimeoutResolver meets the
// ContractResolver interface. // ContractResolver interface.
var _ ContractResolver = (*htlcTimeoutResolver)(nil) var _ htlcContractResolver = (*htlcTimeoutResolver)(nil)
+10
View File
@@ -1,7 +1,10 @@
package contractcourt package contractcourt
import ( import (
"io"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/invoices" "github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
@@ -26,3 +29,10 @@ type Registry interface {
// HodlUnsubscribeAll unsubscribes from all hodl events. // HodlUnsubscribeAll unsubscribes from all hodl events.
HodlUnsubscribeAll(subscriber chan<- interface{}) HodlUnsubscribeAll(subscriber chan<- interface{})
} }
// OnionProcessor is an interface used to decode onion blobs.
type OnionProcessor interface {
// ReconstructHopIterator attempts to decode a valid sphinx packet from
// the passed io.Reader instance.
ReconstructHopIterator(r io.Reader, rHash []byte) (hop.Iterator, error)
}
+24
View File
@@ -184,6 +184,30 @@ func (p *OnionProcessor) DecodeHopIterator(r io.Reader, rHash []byte,
return makeSphinxHopIterator(onionPkt, sphinxPacket), lnwire.CodeNone return makeSphinxHopIterator(onionPkt, sphinxPacket), lnwire.CodeNone
} }
// ReconstructHopIterator attempts to decode a valid sphinx packet from the passed io.Reader
// instance using the rHash as the associated data when checking the relevant
// MACs during the decoding process.
func (p *OnionProcessor) ReconstructHopIterator(r io.Reader, rHash []byte) (
Iterator, error) {
onionPkt := &sphinx.OnionPacket{}
if err := onionPkt.Decode(r); err != nil {
return nil, err
}
// Attempt to process the Sphinx packet. We include the payment hash of
// the HTLC as it's authenticated within the Sphinx packet itself as
// associated data in order to thwart attempts a replay attacks. In the
// case of a replay, an attacker is *forced* to use the same payment
// hash twice, thereby losing their money entirely.
sphinxPacket, err := p.router.ReconstructOnionPacket(onionPkt, rHash)
if err != nil {
return nil, err
}
return makeSphinxHopIterator(onionPkt, sphinxPacket), nil
}
// DecodeHopIteratorRequest encapsulates all date necessary to process an onion // DecodeHopIteratorRequest encapsulates all date necessary to process an onion
// packet, perform sphinx replay detection, and schedule the entry for garbage // packet, perform sphinx replay detection, and schedule the entry for garbage
// collection. // collection.
+1
View File
@@ -906,6 +906,7 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB,
Sweeper: s.sweeper, Sweeper: s.sweeper,
Registry: s.invoices, Registry: s.invoices,
NotifyClosedChannel: s.channelNotifier.NotifyClosedChannelEvent, NotifyClosedChannel: s.channelNotifier.NotifyClosedChannelEvent,
OnionProcessor: s.sphinx,
}, chanDB) }, chanDB)
s.breachArbiter = newBreachArbiter(&BreachConfig{ s.breachArbiter = newBreachArbiter(&BreachConfig{