cnct: parse onion for resolvers
With the introduction of additional payload fields for mpp, it becomes a necessity to have their values available in the on-chain resolution flow. The incoming contest resolver notifies the invoice registry of the arrival of a payment and needs to supply all parameters for the registry to validate the htlc.
This commit is contained in:
parent
ede7e5e7ee
commit
0d7119a8ca
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
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"
|
||||||
@ -65,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)
|
||||||
@ -185,7 +203,7 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
|
|||||||
|
|
||||||
event, err := h.Registry.NotifyExitHopHtlc(
|
event, err := h.Registry.NotifyExitHopHtlc(
|
||||||
h.htlc.RHash, h.htlc.Amt, h.htlcExpiry, currentHeight,
|
h.htlc.RHash, h.htlc.Amt, h.htlcExpiry, currentHeight,
|
||||||
circuitKey, hodlChan, nil,
|
circuitKey, hodlChan, payload,
|
||||||
)
|
)
|
||||||
switch err {
|
switch err {
|
||||||
case channeldb.ErrInvoiceNotFound:
|
case channeldb.ErrInvoiceNotFound:
|
||||||
@ -344,6 +362,20 @@ func (h *htlcIncomingContestResolver) Supplement(htlc channeldb.HTLC) {
|
|||||||
h.htlc = 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 _ htlcContractResolver = (*htlcIncomingContestResolver)(nil)
|
var _ htlcContractResolver = (*htlcIncomingContestResolver)(nil)
|
||||||
|
@ -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,11 +178,36 @@ 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
|
||||||
|
onionProcessor *mockOnionProcessor
|
||||||
resolveErr chan error
|
resolveErr chan error
|
||||||
nextResolver ContractResolver
|
nextResolver ContractResolver
|
||||||
t *testing.T
|
t *testing.T
|
||||||
@ -189,6 +224,8 @@ 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{
|
||||||
@ -196,6 +233,7 @@ func newIncomingResolverTestContext(t *testing.T) *incomingResolverTestContext {
|
|||||||
Notifier: notifier,
|
Notifier: notifier,
|
||||||
PreimageDB: witnessBeacon,
|
PreimageDB: witnessBeacon,
|
||||||
Registry: registry,
|
Registry: registry,
|
||||||
|
OnionProcessor: onionProcessor,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,6 +250,7 @@ func newIncomingResolverTestContext(t *testing.T) *incomingResolverTestContext {
|
|||||||
htlcResolution: lnwallet.IncomingHtlcResolution{},
|
htlcResolution: lnwallet.IncomingHtlcResolution{},
|
||||||
htlc: channeldb.HTLC{
|
htlc: channeldb.HTLC{
|
||||||
RHash: testResHash,
|
RHash: testResHash,
|
||||||
|
OnionBlob: testOnionBlob,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
htlcExpiry: testHtlcExpiry,
|
htlcExpiry: testHtlcExpiry,
|
||||||
@ -222,6 +261,7 @@ func newIncomingResolverTestContext(t *testing.T) *incomingResolverTestContext {
|
|||||||
witnessBeacon: witnessBeacon,
|
witnessBeacon: witnessBeacon,
|
||||||
resolver: resolver,
|
resolver: resolver,
|
||||||
notifier: notifier,
|
notifier: notifier,
|
||||||
|
onionProcessor: onionProcessor,
|
||||||
t: t,
|
t: t,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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{
|
||||||
|
Loading…
Reference in New Issue
Block a user