1513 lines
49 KiB
Go
1513 lines
49 KiB
Go
package htlcswitch
|
|
|
|
import (
|
|
"bytes"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"io"
|
|
|
|
"crypto/sha256"
|
|
|
|
"github.com/go-errors/errors"
|
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
|
"github.com/lightningnetwork/lnd/lnwallet"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
|
"github.com/roasbeef/btcd/wire"
|
|
"github.com/roasbeef/btcutil"
|
|
)
|
|
|
|
const (
|
|
// expiryGraceDelta is a grace period that the timeout of incoming
|
|
// HTLC's that pay directly to us (i.e we're the "exit node") must up
|
|
// hold. We'll reject any HTLC's who's timeout minus this value is less
|
|
// that or equal to the current block height. We require this in order
|
|
// to ensure that if the extending party goes to the chain, then we'll
|
|
// be able to claim the HTLC still.
|
|
//
|
|
// TODO(roasbeef): must be < default delta
|
|
expiryGraceDelta = 2
|
|
)
|
|
|
|
// ForwardingPolicy describes the set of constraints that a given ChannelLink
|
|
// is to adhere to when forwarding HTLC's. For each incoming HTLC, this set of
|
|
// constraints will be consulted in order to ensure that adequate fees are
|
|
// paid, and our time-lock parameters are respected. In the event that an
|
|
// incoming HTLC violates any of these constraints, it is to be _rejected_ with
|
|
// the error possibly carrying along a ChannelUpdate message that includes the
|
|
// latest policy.
|
|
type ForwardingPolicy struct {
|
|
// MinHTLC is the smallest HTLC that is to be forwarded.
|
|
MinHTLC lnwire.MilliSatoshi
|
|
|
|
// BaseFee is the base fee, expressed in milli-satoshi that must be
|
|
// paid for each incoming HTLC. This field, combined with FeeRate is
|
|
// used to compute the required fee for a given HTLC.
|
|
BaseFee lnwire.MilliSatoshi
|
|
|
|
// FeeRate is the fee rate, expressed in milli-satoshi that must be
|
|
// paid for each incoming HTLC. This field combined with BaseFee is
|
|
// used to compute the required fee for a given HTLC.
|
|
FeeRate lnwire.MilliSatoshi
|
|
|
|
// TimeLockDelta is the absolute time-lock value, expressed in blocks,
|
|
// that will be subtracted from an incoming HTLC's timelock value to
|
|
// create the time-lock value for the forwarded outgoing HTLC. The
|
|
// following constraint MUST hold for an HTLC to be forwarded:
|
|
//
|
|
// * incomingHtlc.timeLock - timeLockDelta = fwdInfo.OutgoingCTLV
|
|
//
|
|
// where fwdInfo is the forwarding information extracted from the
|
|
// per-hop payload of the incoming HTLC's onion packet.
|
|
TimeLockDelta uint32
|
|
|
|
// TODO(roasbeef): add fee module inside of switch
|
|
}
|
|
|
|
// ExpectedFee computes the expected fee for a given htlc amount. The value
|
|
// returned from this function is to be used as a sanity check when forwarding
|
|
// HTLC's to ensure that an incoming HTLC properly adheres to our propagated
|
|
// forwarding policy.
|
|
//
|
|
// TODO(roasbeef): also add in current available channel bandwidth, inverse
|
|
// func
|
|
func ExpectedFee(f ForwardingPolicy, htlcAmt lnwire.MilliSatoshi) lnwire.MilliSatoshi {
|
|
|
|
// TODO(roasbeef): write some basic table driven tests
|
|
return f.BaseFee + (htlcAmt*f.FeeRate)/1000000
|
|
}
|
|
|
|
// ChannelLinkConfig defines the configuration for the channel link. ALL
|
|
// elements within the configuration MUST be non-nil for channel link to carry
|
|
// out its duties.
|
|
type ChannelLinkConfig struct {
|
|
// FwrdingPolicy is the initial forwarding policy to be used when
|
|
// deciding whether to forwarding incoming HTLC's or not. This value
|
|
// can be updated with subsequent calls to UpdateForwardingPolicy
|
|
// targeted at a given ChannelLink concrete interface implementation.
|
|
FwrdingPolicy ForwardingPolicy
|
|
|
|
// Switch is a subsystem which is used to forward the incoming HTLC
|
|
// packets according to the encoded hop forwarding information
|
|
// contained in the forwarding blob within each HTLC.
|
|
//
|
|
// TODO(roasbeef): remove in favor of simple ForwardPacket closure func
|
|
Switch *Switch
|
|
|
|
// DecodeHopIterator function is responsible for decoding HTLC Sphinx
|
|
// onion blob, and creating hop iterator which will give us next
|
|
// destination of HTLC.
|
|
DecodeHopIterator func(r io.Reader, rHash []byte) (HopIterator, lnwire.FailCode)
|
|
|
|
// DecodeOnionObfuscator function is responsible for decoding HTLC
|
|
// Sphinx onion blob, and creating onion failure obfuscator.
|
|
DecodeOnionObfuscator func(r io.Reader) (ErrorEncrypter, lnwire.FailCode)
|
|
|
|
// GetLastChannelUpdate retrieves the latest routing policy for this
|
|
// particular channel. This will be used to provide payment senders our
|
|
// latest policy when sending encrypted error messages.
|
|
GetLastChannelUpdate func() (*lnwire.ChannelUpdate, error)
|
|
|
|
// Peer is a lightning network node with which we have the channel link
|
|
// opened.
|
|
Peer Peer
|
|
|
|
// Registry is a sub-system which responsible for managing the invoices
|
|
// in thread-safe manner.
|
|
Registry InvoiceDatabase
|
|
|
|
// BlockEpochs is an active block epoch event stream backed by an
|
|
// active ChainNotifier instance. The ChannelLink will use new block
|
|
// notifications sent over this channel to decide when a _new_ HTLC is
|
|
// too close to expiry, and also when any active HTLC's have expired
|
|
// (or are close to expiry).
|
|
BlockEpochs *chainntnfs.BlockEpochEvent
|
|
|
|
// SettledContracts is used to notify that a channel has peacefully
|
|
// been closed. Once a channel has been closed the other subsystem no
|
|
// longer needs to watch for breach closes.
|
|
SettledContracts chan *wire.OutPoint
|
|
|
|
// DebugHTLC should be turned on if you want all HTLCs sent to a node
|
|
// with the debug htlc R-Hash are immediately settled in the next
|
|
// available state transition.
|
|
DebugHTLC bool
|
|
|
|
// HodlHTLC should be active if you want this node to refrain from
|
|
// settling all incoming HTLCs with the sender if it finds itself to be
|
|
// the exit node.
|
|
// NOTE: HodlHTLC should be active in conjunction with DebugHTLC.
|
|
HodlHTLC bool
|
|
}
|
|
|
|
// channelLink is the service which drives a channel's commitment update
|
|
// state-machine. In the event that an htlc needs to be propagated to another
|
|
// link, the forward handler from config is used which sends htlc to the
|
|
// switch. Additionally, the link encapsulate logic of commitment protocol
|
|
// message ordering and updates.
|
|
type channelLink struct {
|
|
// The following fields are only meant to be used *atomically*
|
|
started int32
|
|
shutdown int32
|
|
|
|
// cancelReasons stores the reason why a particular HTLC was cancelled.
|
|
// The index of the HTLC within the log is mapped to the cancellation
|
|
// reason. This value is used to thread the proper error through to the
|
|
// htlcSwitch, or subsystem that initiated the HTLC.
|
|
//
|
|
// TODO(andrew.shvv) remove after payment descriptor start store
|
|
// htlc cancel reasons.
|
|
cancelReasons map[uint64]lnwire.OpaqueReason
|
|
|
|
// clearedOnionBlobs tracks the remote log index of the incoming
|
|
// htlc's, mapped to the htlc onion blob which encapsulates the next
|
|
// hop. HTLC's are added to this map once the HTLC has been cleared,
|
|
// meaning the commitment state reflects the update encoded within this
|
|
// HTLC.
|
|
//
|
|
// TODO(andrew.shvv) remove after payment descriptor start store
|
|
// htlc onion blobs.
|
|
clearedOnionBlobs map[uint64][lnwire.OnionPacketSize]byte
|
|
|
|
// batchCounter is the number of updates which we received from remote
|
|
// side, but not include in commitment transaction yet and plus the
|
|
// current number of settles that have been sent, but not yet committed
|
|
// to the commitment.
|
|
//
|
|
// TODO(andrew.shvv) remove after we add additional
|
|
// BatchNumber() method in state machine.
|
|
batchCounter uint32
|
|
|
|
// bestHeight is the best known height of the main chain. The link will
|
|
// use this information to govern decisions based on HTLC timeouts.
|
|
bestHeight uint32
|
|
|
|
// channel is a lightning network channel to which we apply htlc
|
|
// updates.
|
|
channel *lnwallet.LightningChannel
|
|
|
|
// cfg is a structure which carries all dependable fields/handlers
|
|
// which may affect behaviour of the service.
|
|
cfg ChannelLinkConfig
|
|
|
|
// overflowQueue is used to store the htlc add updates which haven't
|
|
// been processed because of the commitment transaction overflow.
|
|
overflowQueue *packetQueue
|
|
|
|
// availableBandwidth is an integer with units of millisatoshi which
|
|
// indicates the total available bandwidth of a link, taking into
|
|
// account any pending (uncommitted) HLTC's, and any HTLC's that are
|
|
// within the overflow queue.
|
|
availableBandwidth uint64
|
|
|
|
// upstream is a channel that new messages sent from the remote peer to
|
|
// the local peer will be sent across.
|
|
upstream chan lnwire.Message
|
|
|
|
// downstream is a channel in which new multi-hop HTLC's to be
|
|
// forwarded will be sent across. Messages from this channel are sent
|
|
// by the HTLC switch.
|
|
downstream chan *htlcPacket
|
|
|
|
// linkControl is a channel which is used to query the state of the
|
|
// link, or update various policies used which govern if an HTLC is to
|
|
// be forwarded and/or accepted.
|
|
linkControl chan interface{}
|
|
|
|
// logCommitTimer is a timer which is sent upon if we go an interval
|
|
// without receiving/sending a commitment update. It's role is to
|
|
// ensure both chains converge to identical state in a timely manner.
|
|
//
|
|
// TODO(roasbeef): timer should be >> then RTT
|
|
logCommitTimer *time.Timer
|
|
logCommitTick <-chan time.Time
|
|
|
|
wg sync.WaitGroup
|
|
quit chan struct{}
|
|
}
|
|
|
|
// NewChannelLink creates a new instance of a ChannelLink given a configuration
|
|
// and active channel that will be used to verify/apply updates to.
|
|
func NewChannelLink(cfg ChannelLinkConfig, channel *lnwallet.LightningChannel,
|
|
currentHeight uint32) ChannelLink {
|
|
|
|
return &channelLink{
|
|
cfg: cfg,
|
|
channel: channel,
|
|
clearedOnionBlobs: make(map[uint64][lnwire.OnionPacketSize]byte),
|
|
upstream: make(chan lnwire.Message),
|
|
downstream: make(chan *htlcPacket),
|
|
linkControl: make(chan interface{}),
|
|
// TODO(roasbeef): just do reserve here?
|
|
availableBandwidth: uint64(channel.StateSnapshot().LocalBalance),
|
|
cancelReasons: make(map[uint64]lnwire.OpaqueReason),
|
|
logCommitTimer: time.NewTimer(300 * time.Millisecond),
|
|
overflowQueue: newPacketQueue(lnwallet.MaxHTLCNumber / 2),
|
|
bestHeight: currentHeight,
|
|
quit: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
// A compile time check to ensure channelLink implements the ChannelLink
|
|
// interface.
|
|
var _ ChannelLink = (*channelLink)(nil)
|
|
|
|
// Start starts all helper goroutines required for the operation of the channel
|
|
// link.
|
|
//
|
|
// NOTE: Part of the ChannelLink interface.
|
|
func (l *channelLink) Start() error {
|
|
if !atomic.CompareAndSwapInt32(&l.started, 0, 1) {
|
|
log.Warnf("channel link(%v): already started", l)
|
|
return nil
|
|
}
|
|
|
|
log.Infof("ChannelLink(%v) is starting", l)
|
|
|
|
l.overflowQueue.Start()
|
|
|
|
l.wg.Add(1)
|
|
go l.htlcManager()
|
|
|
|
return nil
|
|
}
|
|
|
|
// Stop gracefully stops all active helper goroutines, then waits until they've
|
|
// exited.
|
|
//
|
|
// NOTE: Part of the ChannelLink interface.
|
|
func (l *channelLink) Stop() {
|
|
if !atomic.CompareAndSwapInt32(&l.shutdown, 0, 1) {
|
|
log.Warnf("channel link(%v): already stopped", l)
|
|
return
|
|
}
|
|
|
|
log.Infof("ChannelLink(%v) is stopping", l)
|
|
|
|
l.channel.Stop()
|
|
|
|
l.overflowQueue.Stop()
|
|
|
|
close(l.quit)
|
|
l.wg.Wait()
|
|
|
|
l.cfg.BlockEpochs.Cancel()
|
|
}
|
|
|
|
// htlcManager is the primary goroutine which drives a channel's commitment
|
|
// update state-machine in response to messages received via several channels.
|
|
// This goroutine reads messages from the upstream (remote) peer, and also from
|
|
// downstream channel managed by the channel link. In the event that an htlc
|
|
// needs to be forwarded, then send-only forward handler is used which sends
|
|
// htlc packets to the switch. Additionally, the this goroutine handles acting
|
|
// upon all timeouts for any active HTLCs, manages the channel's revocation
|
|
// window, and also the htlc trickle queue+timer for this active channels.
|
|
//
|
|
// NOTE: This MUST be run as a goroutine.
|
|
func (l *channelLink) htlcManager() {
|
|
defer l.wg.Done()
|
|
|
|
log.Infof("HTLC manager for ChannelPoint(%v) started, "+
|
|
"bandwidth=%v", l.channel.ChannelPoint(), l.Bandwidth())
|
|
|
|
// TODO(roasbeef): check to see if able to settle any currently pending
|
|
// HTLCs
|
|
// * also need signals when new invoices are added by the
|
|
// invoiceRegistry
|
|
|
|
batchTimer := time.NewTicker(50 * time.Millisecond)
|
|
defer batchTimer.Stop()
|
|
|
|
// TODO(roasbeef): fail chan in case of protocol violation
|
|
|
|
// If the number of updates on this channel has been zero, we should
|
|
// resend the fundingLocked message. This is because in this case we
|
|
// cannot be sure if the peer really received the last fundingLocked we
|
|
// sent, so resend now.
|
|
if l.channel.StateSnapshot().NumUpdates == 0 {
|
|
log.Debugf("Resending fundingLocked message to peer.")
|
|
|
|
nextRevocation, err := l.channel.NextRevocationKey()
|
|
if err != nil {
|
|
log.Errorf("unable to create next revocation: %v", err)
|
|
}
|
|
|
|
fundingLockedMsg := lnwire.NewFundingLocked(l.ChanID(),
|
|
nextRevocation)
|
|
err = l.cfg.Peer.SendMessage(fundingLockedMsg)
|
|
if err != nil {
|
|
log.Errorf("failed resending fundingLocked to peer: %v",
|
|
err)
|
|
}
|
|
}
|
|
|
|
out:
|
|
for {
|
|
select {
|
|
// A new block has arrived, we'll examine all the active HTLC's
|
|
// to see if any of them have expired, and also update our
|
|
// track of the best current height.
|
|
case blockEpoch, ok := <-l.cfg.BlockEpochs.Epochs:
|
|
if !ok {
|
|
break out
|
|
}
|
|
|
|
log.Tracef("ChannelPoint(%v): new block(height=%v, "+
|
|
"hash=%v) examining active HTLC's",
|
|
l.channel.ChannelPoint(), blockEpoch.Height,
|
|
blockEpoch.Hash)
|
|
|
|
// TODO(roasbeef): check HTLC's for expiry
|
|
l.bestHeight = uint32(blockEpoch.Height)
|
|
|
|
// The underlying channel has notified us of a unilateral close
|
|
// carried out by the remote peer. In the case of such an
|
|
// event, we'll wipe the channel state from the peer, and mark
|
|
// the contract as fully settled. Afterwards we can exit.
|
|
case <-l.channel.UnilateralCloseSignal:
|
|
log.Warnf("Remote peer has closed ChannelPoint(%v) on-chain",
|
|
l.channel.ChannelPoint())
|
|
if err := l.cfg.Peer.WipeChannel(l.channel); err != nil {
|
|
log.Errorf("unable to wipe channel %v", err)
|
|
}
|
|
|
|
// TODO(roasbeef): need to send HTLC outputs to nursery
|
|
|
|
// TODO(roasbeef): or let the arb sweep?
|
|
l.cfg.SettledContracts <- l.channel.ChannelPoint()
|
|
break out
|
|
|
|
// A local sub-system has initiated a force close of the active
|
|
// channel. In this case we can exit immediately as no further
|
|
// updates should be processed for the channel.
|
|
case <-l.channel.ForceCloseSignal:
|
|
// TODO(roasbeef): path never taken now that server
|
|
// force closes's directly?
|
|
log.Warnf("ChannelPoint(%v) has been force "+
|
|
"closed, disconnecting from peer(%x)",
|
|
l.channel.ChannelPoint(), l.cfg.Peer.PubKey())
|
|
break out
|
|
|
|
case <-l.logCommitTick:
|
|
// If we haven't sent or received a new commitment
|
|
// update in some time, check to see if we have any
|
|
// pending updates we need to commit due to our
|
|
// commitment chains being desynchronized.
|
|
if l.channel.FullySynced() {
|
|
continue
|
|
}
|
|
|
|
if err := l.updateCommitTx(); err != nil {
|
|
l.fail("unable to update commitment: %v", err)
|
|
break out
|
|
}
|
|
|
|
case <-batchTimer.C:
|
|
// If the current batch is empty, then we have no work
|
|
// here.
|
|
if l.batchCounter == 0 {
|
|
continue
|
|
}
|
|
|
|
// Otherwise, attempt to extend the remote commitment
|
|
// chain including all the currently pending entries.
|
|
// If the send was unsuccessful, then abandon the
|
|
// update, waiting for the revocation window to open
|
|
// up.
|
|
if err := l.updateCommitTx(); err != nil {
|
|
l.fail("unable to update commitment: %v", err)
|
|
break out
|
|
}
|
|
|
|
// A packet that previously overflowed the commitment
|
|
// transaction is now eligible for processing once again. So
|
|
// we'll attempt to re-process the packet in order to allow it
|
|
// to continue propagating within the network.
|
|
case packet := <-l.overflowQueue.outgoingPkts:
|
|
msg := packet.htlc.(*lnwire.UpdateAddHTLC)
|
|
log.Tracef("Reprocessing downstream add update "+
|
|
"with payment hash(%x)", msg.PaymentHash[:])
|
|
|
|
l.handleDownStreamPkt(packet, true)
|
|
|
|
// A message from the switch was just received. This indicates
|
|
// that the link is an intermediate hop in a multi-hop HTLC
|
|
// circuit.
|
|
case pkt := <-l.downstream:
|
|
// If we have non empty processing queue then we'll add
|
|
// this to the overflow rather than processing it
|
|
// directly. Once an active HTLC is either settled or
|
|
// failed, then we'll free up a new slot.
|
|
htlc, ok := pkt.htlc.(*lnwire.UpdateAddHTLC)
|
|
if ok && l.overflowQueue.Length() != 0 {
|
|
log.Infof("Downstream htlc add update with "+
|
|
"payment hash(%x) have been added to "+
|
|
"reprocessing queue, batch_size=%v",
|
|
htlc.PaymentHash[:],
|
|
l.batchCounter)
|
|
|
|
// As we're adding a new pkt to the overflow
|
|
// queue, decrement the available bandwidth.
|
|
atomic.AddUint64(
|
|
&l.availableBandwidth,
|
|
-uint64(htlc.Amount),
|
|
)
|
|
l.overflowQueue.AddPkt(pkt)
|
|
continue
|
|
}
|
|
l.handleDownStreamPkt(pkt, false)
|
|
|
|
// A message from the connected peer was just received. This
|
|
// indicates that we have a new incoming HTLC, either directly
|
|
// for us, or part of a multi-hop HTLC circuit.
|
|
case msg := <-l.upstream:
|
|
l.handleUpstreamMsg(msg)
|
|
|
|
case cmd := <-l.linkControl:
|
|
switch req := cmd.(type) {
|
|
case *policyUpdate:
|
|
// In order to avoid overriding a valid policy
|
|
// with a "null" field in the new policy, we'll
|
|
// only update to the set sub policy if the new
|
|
// value isn't uninitialized.
|
|
if req.policy.MinHTLC != 0 {
|
|
l.cfg.FwrdingPolicy.MinHTLC = req.policy.MinHTLC
|
|
}
|
|
if req.policy.BaseFee != 0 {
|
|
l.cfg.FwrdingPolicy.BaseFee = req.policy.BaseFee
|
|
}
|
|
if req.policy.FeeRate != 0 {
|
|
l.cfg.FwrdingPolicy.FeeRate = req.policy.FeeRate
|
|
}
|
|
if req.policy.TimeLockDelta != 0 {
|
|
l.cfg.FwrdingPolicy.TimeLockDelta = req.policy.TimeLockDelta
|
|
}
|
|
|
|
if req.done != nil {
|
|
close(req.done)
|
|
}
|
|
}
|
|
|
|
case <-l.quit:
|
|
break out
|
|
}
|
|
}
|
|
|
|
log.Infof("ChannelLink(%v) has exited", l)
|
|
}
|
|
|
|
// handleDownStreamPkt processes an HTLC packet sent from the downstream HTLC
|
|
// Switch. Possible messages sent by the switch include requests to forward new
|
|
// HTLCs, timeout previously cleared HTLCs, and finally to settle currently
|
|
// cleared HTLCs with the upstream peer.
|
|
//
|
|
// TODO(roasbeef): add sync ntfn to ensure switch always has consistent view?
|
|
func (l *channelLink) handleDownStreamPkt(pkt *htlcPacket, isReProcess bool) {
|
|
var isSettle bool
|
|
switch htlc := pkt.htlc.(type) {
|
|
case *lnwire.UpdateAddHTLC:
|
|
// A new payment has been initiated via the downstream channel,
|
|
// so we add the new HTLC to our local log, then update the
|
|
// commitment chains.
|
|
htlc.ChanID = l.ChanID()
|
|
index, err := l.channel.AddHTLC(htlc)
|
|
if err != nil {
|
|
switch err {
|
|
|
|
// The channels spare bandwidth is fully allocated, so
|
|
// we'll put this HTLC into the overflow queue.
|
|
case lnwallet.ErrMaxHTLCNumber:
|
|
log.Infof("Downstream htlc add update with "+
|
|
"payment hash(%x) have been added to "+
|
|
"reprocessing queue, batch: %v",
|
|
htlc.PaymentHash[:],
|
|
l.batchCounter)
|
|
|
|
// If we're processing this HTLC for the first
|
|
// time, then we'll decrement the available
|
|
// bandwidth. As otherwise, we'd double count
|
|
// the effect of the HTLC.
|
|
if !isReProcess {
|
|
atomic.AddUint64(
|
|
&l.availableBandwidth, -uint64(htlc.Amount),
|
|
)
|
|
}
|
|
|
|
l.overflowQueue.AddPkt(pkt)
|
|
return
|
|
|
|
// The HTLC was unable to be added to the state
|
|
// machine, as a result, we'll signal the switch to
|
|
// cancel the pending payment.
|
|
default:
|
|
var (
|
|
isObfuscated bool
|
|
reason lnwire.OpaqueReason
|
|
)
|
|
|
|
// We'll parse the sphinx packet enclosed so we
|
|
// can obtain the shared secret required to
|
|
// encrypt the error back to the source.
|
|
failure := lnwire.NewTemporaryChannelFailure(nil)
|
|
onionReader := bytes.NewReader(htlc.OnionBlob[:])
|
|
obfuscator, failCode := l.cfg.DecodeOnionObfuscator(onionReader)
|
|
|
|
switch {
|
|
// If we were unable to parse the onion blob,
|
|
// then we'll send an error back to the source.
|
|
case failCode != lnwire.CodeNone:
|
|
var b bytes.Buffer
|
|
err := lnwire.EncodeFailure(&b, failure, 0)
|
|
if err != nil {
|
|
log.Errorf("unable to encode failure: %v", err)
|
|
return
|
|
}
|
|
reason = lnwire.OpaqueReason(b.Bytes())
|
|
isObfuscated = false
|
|
|
|
// Otherwise, we'll send back a proper failure
|
|
// message.
|
|
default:
|
|
reason, err = obfuscator.EncryptFirstHop(failure)
|
|
if err != nil {
|
|
log.Errorf("unable to obfuscate error: %v", err)
|
|
return
|
|
}
|
|
isObfuscated = true
|
|
}
|
|
|
|
upddateFail := &lnwire.UpdateFailHTLC{
|
|
Reason: reason,
|
|
}
|
|
failPkt := newFailPacket(
|
|
l.ShortChanID(), upddateFail,
|
|
htlc.PaymentHash, htlc.Amount,
|
|
isObfuscated,
|
|
)
|
|
|
|
atomic.AddUint64(&l.availableBandwidth, uint64(htlc.Amount))
|
|
|
|
// TODO(roasbeef): need to identify if sent
|
|
// from switch so don't need to obfuscate
|
|
go l.cfg.Switch.forward(failPkt)
|
|
log.Infof("Unable to handle downstream add HTLC: %v", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
// If we're processing this HTLC for the first time, then we'll
|
|
// decrement the available bandwidth.
|
|
if !isReProcess {
|
|
// Subtract the available bandwidth as we have a new
|
|
// HTLC in limbo.
|
|
atomic.AddUint64(&l.availableBandwidth, -uint64(htlc.Amount))
|
|
}
|
|
|
|
log.Tracef("Received downstream htlc: payment_hash=%x, "+
|
|
"local_log_index=%v, batch_size=%v",
|
|
htlc.PaymentHash[:], index, l.batchCounter+1)
|
|
|
|
htlc.ID = index
|
|
l.cfg.Peer.SendMessage(htlc)
|
|
|
|
case *lnwire.UpdateFufillHTLC:
|
|
// An HTLC we forward to the switch has just settled somewhere
|
|
// upstream. Therefore we settle the HTLC within the our local
|
|
// state machine.
|
|
pre := htlc.PaymentPreimage
|
|
logIndex, amt, err := l.channel.SettleHTLC(pre)
|
|
if err != nil {
|
|
// TODO(roasbeef): broadcast on-chain
|
|
l.fail("unable to settle incoming HTLC: %v", err)
|
|
return
|
|
}
|
|
|
|
// Increment the available bandwidth as we've settled an HTLC
|
|
// extended by tbe remote party.
|
|
atomic.AddUint64(&l.availableBandwidth, uint64(amt))
|
|
|
|
// With the HTLC settled, we'll need to populate the wire
|
|
// message to target the specific channel and HTLC to be
|
|
// cancelled.
|
|
htlc.ChanID = l.ChanID()
|
|
htlc.ID = logIndex
|
|
|
|
// Then we send the HTLC settle message to the connected peer
|
|
// so we can continue the propagation of the settle message.
|
|
l.cfg.Peer.SendMessage(htlc)
|
|
isSettle = true
|
|
|
|
case *lnwire.UpdateFailHTLC:
|
|
// An HTLC cancellation has been triggered somewhere upstream,
|
|
// we'll remove then HTLC from our local state machine.
|
|
logIndex, err := l.channel.FailHTLC(pkt.payHash)
|
|
if err != nil {
|
|
log.Errorf("unable to cancel HTLC: %v", err)
|
|
return
|
|
}
|
|
|
|
// With the HTLC removed, we'll need to populate the wire
|
|
// message to target the specific channel and HTLC to be
|
|
// cancelled. The "Reason" field will have already been set
|
|
// within the switch.
|
|
htlc.ChanID = l.ChanID()
|
|
htlc.ID = logIndex
|
|
|
|
// Finally, we send the HTLC message to the peer which
|
|
// initially created the HTLC.
|
|
l.cfg.Peer.SendMessage(htlc)
|
|
isSettle = true
|
|
}
|
|
|
|
l.batchCounter++
|
|
|
|
// If this newly added update exceeds the min batch size for adds, or
|
|
// this is a settle request, then initiate an update.
|
|
if l.batchCounter >= 10 || isSettle {
|
|
if err := l.updateCommitTx(); err != nil {
|
|
l.fail("unable to update commitment: %v", err)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// handleUpstreamMsg processes wire messages related to commitment state
|
|
// updates from the upstream peer. The upstream peer is the peer whom we have a
|
|
// direct channel with, updating our respective commitment chains.
|
|
func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) {
|
|
switch msg := msg.(type) {
|
|
case *lnwire.UpdateAddHTLC:
|
|
// We just received an add request from an upstream peer, so we
|
|
// add it to our state machine, then add the HTLC to our
|
|
// "settle" list in the event that we know the preimage.
|
|
index, err := l.channel.ReceiveHTLC(msg)
|
|
if err != nil {
|
|
l.fail("unable to handle upstream add HTLC: %v", err)
|
|
return
|
|
}
|
|
log.Tracef("Receive upstream htlc with payment hash(%x), "+
|
|
"assigning index: %v", msg.PaymentHash[:], index)
|
|
|
|
// Store the onion blob which encapsulate the htlc route and
|
|
// use in on stage of HTLC inclusion to retrieve the next hop
|
|
// and propagate the HTLC along the remaining route.
|
|
l.clearedOnionBlobs[index] = msg.OnionBlob
|
|
|
|
case *lnwire.UpdateFufillHTLC:
|
|
pre := msg.PaymentPreimage
|
|
idx := msg.ID
|
|
if err := l.channel.ReceiveHTLCSettle(pre, idx); err != nil {
|
|
// TODO(roasbeef): broadcast on-chain
|
|
l.fail("unable to handle upstream settle HTLC: %v", err)
|
|
return
|
|
}
|
|
|
|
// TODO(roasbeef): add preimage to DB in order to swipe
|
|
// repeated r-values
|
|
|
|
case *lnwire.UpdateFailMalformedHTLC:
|
|
// If remote side have been unable to parse the onion blob we
|
|
// have sent to it, than we should transform the malformed HTLC
|
|
// message to the usual HTLC fail message.
|
|
idx := msg.ID
|
|
amt, err := l.channel.ReceiveFailHTLC(idx)
|
|
if err != nil {
|
|
l.fail("unable to handle upstream fail HTLC: %v", err)
|
|
return
|
|
}
|
|
|
|
// Increment the available bandwidth as they've removed our
|
|
// HTLC.
|
|
atomic.AddUint64(&l.availableBandwidth, uint64(amt))
|
|
|
|
// Convert the failure type encoded within the HTLC fail
|
|
// message to the proper generic lnwire error code.
|
|
var failure lnwire.FailureMessage
|
|
switch msg.FailureCode {
|
|
case lnwire.CodeInvalidOnionVersion:
|
|
failure = &lnwire.FailInvalidOnionVersion{
|
|
OnionSHA256: msg.ShaOnionBlob,
|
|
}
|
|
case lnwire.CodeInvalidOnionHmac:
|
|
failure = &lnwire.FailInvalidOnionHmac{
|
|
OnionSHA256: msg.ShaOnionBlob,
|
|
}
|
|
|
|
case lnwire.CodeInvalidOnionKey:
|
|
failure = &lnwire.FailInvalidOnionKey{
|
|
OnionSHA256: msg.ShaOnionBlob,
|
|
}
|
|
default:
|
|
// TODO(roasbeef): fail channel here?
|
|
log.Errorf("unable to understand code of received " +
|
|
"malformed error")
|
|
return
|
|
}
|
|
|
|
// With the error parsed, we'll convert the into it's opaque
|
|
// form.
|
|
var b bytes.Buffer
|
|
if err := lnwire.EncodeFailure(&b, failure, 0); err != nil {
|
|
log.Errorf("unable to encode malformed error: %v", err)
|
|
return
|
|
}
|
|
|
|
l.cancelReasons[idx] = lnwire.OpaqueReason(b.Bytes())
|
|
|
|
case *lnwire.UpdateFailHTLC:
|
|
idx := msg.ID
|
|
amt, err := l.channel.ReceiveFailHTLC(idx)
|
|
if err != nil {
|
|
l.fail("unable to handle upstream fail HTLC: %v", err)
|
|
return
|
|
}
|
|
|
|
// Increment the available bandwidth as they've removed our
|
|
// HTLC.
|
|
atomic.AddUint64(&l.availableBandwidth, uint64(amt))
|
|
|
|
l.cancelReasons[idx] = msg.Reason
|
|
|
|
case *lnwire.CommitSig:
|
|
// We just received a new update to our local commitment chain,
|
|
// validate this new commitment, closing the link if invalid.
|
|
err := l.channel.ReceiveNewCommitment(msg.CommitSig, msg.HtlcSigs)
|
|
if err != nil {
|
|
l.fail("unable to accept new commitment: %v", err)
|
|
return
|
|
}
|
|
|
|
// As we've just just accepted a new state, we'll now
|
|
// immediately send the remote peer a revocation for our prior
|
|
// state.
|
|
nextRevocation, err := l.channel.RevokeCurrentCommitment()
|
|
if err != nil {
|
|
log.Errorf("unable to revoke commitment: %v", err)
|
|
return
|
|
}
|
|
l.cfg.Peer.SendMessage(nextRevocation)
|
|
|
|
// As we've just received a commitment signature, we'll
|
|
// re-start the log commit timer to wake up the main processing
|
|
// loop to check if we need to send a commitment signature as
|
|
// we owe one.
|
|
//
|
|
// TODO(roasbeef): instead after revocation?
|
|
if !l.logCommitTimer.Stop() {
|
|
select {
|
|
case <-l.logCommitTimer.C:
|
|
default:
|
|
}
|
|
}
|
|
l.logCommitTimer.Reset(300 * time.Millisecond)
|
|
l.logCommitTick = l.logCommitTimer.C
|
|
|
|
// If both commitment chains are fully synced from our PoV,
|
|
// then we don't need to reply with a signature as both sides
|
|
// already have a commitment with the latest accepted l.
|
|
if l.channel.FullySynced() {
|
|
return
|
|
}
|
|
|
|
// Otherwise, the remote party initiated the state transition,
|
|
// so we'll reply with a signature to provide them with their
|
|
// version of the latest commitment l.
|
|
if err := l.updateCommitTx(); err != nil {
|
|
l.fail("unable to update commitment: %v", err)
|
|
return
|
|
}
|
|
|
|
case *lnwire.RevokeAndAck:
|
|
// We've received a revocation from the remote chain, if valid,
|
|
// this moves the remote chain forward, and expands our
|
|
// revocation window.
|
|
htlcs, err := l.channel.ReceiveRevocation(msg)
|
|
if err != nil {
|
|
l.fail("unable to accept revocation: %v", err)
|
|
return
|
|
}
|
|
|
|
// After we treat HTLCs as included in both remote/local
|
|
// commitment transactions they might be safely propagated over
|
|
// htlc switch or settled if our node was last node in htlc
|
|
// path.
|
|
htlcsToForward := l.processLockedInHtlcs(htlcs)
|
|
go func() {
|
|
log.Debugf("ChannelPoint(%v) forwarding %v HTLC's",
|
|
l.channel.ChannelPoint(), len(htlcsToForward))
|
|
for _, packet := range htlcsToForward {
|
|
if err := l.cfg.Switch.forward(packet); err != nil {
|
|
log.Errorf("channel link(%v): "+
|
|
"unhandled error while forwarding "+
|
|
"htlc packet over htlc "+
|
|
"switch: %v", l, err)
|
|
}
|
|
}
|
|
}()
|
|
case *lnwire.UpdateFee:
|
|
// We received fee update from peer. If we are the initator we
|
|
// will fail the channel, if not we will apply the update.
|
|
fee := msg.FeePerKw
|
|
if err := l.channel.ReceiveUpdateFee(fee); err != nil {
|
|
l.fail("error receiving fee update: %v", err)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// updateCommitTx signs, then sends an update to the remote peer adding a new
|
|
// commitment to their commitment chain which includes all the latest updates
|
|
// we've received+processed up to this point.
|
|
func (l *channelLink) updateCommitTx() error {
|
|
theirCommitSig, htlcSigs, err := l.channel.SignNextCommitment()
|
|
if err == lnwallet.ErrNoWindow {
|
|
log.Tracef("revocation window exhausted, unable to send %v",
|
|
l.batchCounter)
|
|
return nil
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
|
|
commitSig := &lnwire.CommitSig{
|
|
ChanID: l.ChanID(),
|
|
CommitSig: theirCommitSig,
|
|
HtlcSigs: htlcSigs,
|
|
}
|
|
l.cfg.Peer.SendMessage(commitSig)
|
|
|
|
// We've just initiated a state transition, attempt to stop the
|
|
// logCommitTimer. If the timer already ticked, then we'll consume the
|
|
// value, dropping
|
|
if l.logCommitTimer != nil && !l.logCommitTimer.Stop() {
|
|
select {
|
|
case <-l.logCommitTimer.C:
|
|
default:
|
|
}
|
|
}
|
|
l.logCommitTick = nil
|
|
|
|
// Finally, clear our the current batch, so we can accurately make
|
|
// further batch flushing decisions.
|
|
l.batchCounter = 0
|
|
|
|
return nil
|
|
}
|
|
|
|
// Peer returns the representation of remote peer with which we have the
|
|
// channel link opened.
|
|
//
|
|
// NOTE: Part of the ChannelLink interface.
|
|
func (l *channelLink) Peer() Peer {
|
|
return l.cfg.Peer
|
|
}
|
|
|
|
// ShortChanID returns the short channel ID for the channel link. The short
|
|
// channel ID encodes the exact location in the main chain that the original
|
|
// funding output can be found.
|
|
//
|
|
// NOTE: Part of the ChannelLink interface.
|
|
func (l *channelLink) ShortChanID() lnwire.ShortChannelID {
|
|
return l.channel.ShortChanID()
|
|
}
|
|
|
|
// ChanID returns the channel ID for the channel link. The channel ID is a more
|
|
// compact representation of a channel's full outpoint.
|
|
//
|
|
// NOTE: Part of the ChannelLink interface.
|
|
func (l *channelLink) ChanID() lnwire.ChannelID {
|
|
return lnwire.NewChanIDFromOutPoint(l.channel.ChannelPoint())
|
|
}
|
|
|
|
// getBandwidthCmd is a wrapper for get bandwidth handler.
|
|
type getBandwidthCmd struct {
|
|
resp chan lnwire.MilliSatoshi
|
|
}
|
|
|
|
// Bandwidth returns the total amount that can flow through the channel link at
|
|
// this given instance. The value returned is expressed in millatoshi and
|
|
// can be used by callers when making forwarding decisions to determine if a
|
|
// link can accept an HTLC.
|
|
//
|
|
// NOTE: Part of the ChannelLink interface.
|
|
func (l *channelLink) Bandwidth() lnwire.MilliSatoshi {
|
|
// TODO(roasbeef): subtract reserverj
|
|
return lnwire.MilliSatoshi(atomic.LoadUint64(&l.availableBandwidth))
|
|
}
|
|
|
|
// policyUpdate is a message sent to a channel link when an outside sub-system
|
|
// wishes to update the current forwarding policy.
|
|
type policyUpdate struct {
|
|
policy ForwardingPolicy
|
|
|
|
done chan struct{}
|
|
}
|
|
|
|
// UpdateForwardingPolicy updates the forwarding policy for the target
|
|
// ChannelLink. Once updated, the link will use the new forwarding policy to
|
|
// govern if it an incoming HTLC should be forwarded or not. Note that this
|
|
// processing of the new policy will ensure that uninitialized fields in the
|
|
// passed policy won't override already initialized fields in the current
|
|
// policy.
|
|
//
|
|
// NOTE: Part of the ChannelLink interface.
|
|
func (l *channelLink) UpdateForwardingPolicy(newPolicy ForwardingPolicy) {
|
|
cmd := &policyUpdate{
|
|
policy: newPolicy,
|
|
done: make(chan struct{}),
|
|
}
|
|
|
|
select {
|
|
case l.linkControl <- cmd:
|
|
case <-l.quit:
|
|
}
|
|
|
|
select {
|
|
case <-cmd.done:
|
|
case <-l.quit:
|
|
}
|
|
}
|
|
|
|
// Stats returns the statistics of channel link.
|
|
//
|
|
// NOTE: Part of the ChannelLink interface.
|
|
func (l *channelLink) Stats() (uint64, lnwire.MilliSatoshi, lnwire.MilliSatoshi) {
|
|
snapshot := l.channel.StateSnapshot()
|
|
|
|
return snapshot.NumUpdates,
|
|
snapshot.TotalMilliSatoshisSent,
|
|
snapshot.TotalMilliSatoshisReceived
|
|
}
|
|
|
|
// String returns the string representation of channel link.
|
|
//
|
|
// NOTE: Part of the ChannelLink interface.
|
|
func (l *channelLink) String() string {
|
|
return l.channel.ChannelPoint().String()
|
|
}
|
|
|
|
// HandleSwitchPacket handles the switch packets. This packets which might be
|
|
// forwarded to us from another channel link in case the htlc update came from
|
|
// another peer or if the update was created by user
|
|
//
|
|
// NOTE: Part of the ChannelLink interface.
|
|
func (l *channelLink) HandleSwitchPacket(packet *htlcPacket) {
|
|
select {
|
|
case l.downstream <- packet:
|
|
case <-l.quit:
|
|
}
|
|
}
|
|
|
|
// HandleChannelUpdate handles the htlc requests as settle/add/fail which sent
|
|
// to us from remote peer we have a channel with.
|
|
//
|
|
// NOTE: Part of the ChannelLink interface.
|
|
func (l *channelLink) HandleChannelUpdate(message lnwire.Message) {
|
|
select {
|
|
case l.upstream <- message:
|
|
case <-l.quit:
|
|
}
|
|
}
|
|
|
|
// updateChannelFee updates the commitment fee-per-kw on this channel by
|
|
// committing to an update_fee message.
|
|
func (l *channelLink) updateChannelFee(feePerKw btcutil.Amount) error {
|
|
// Update local fee.
|
|
if err := l.channel.UpdateFee(feePerKw); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Send fee update to remote.
|
|
msg := lnwire.NewUpdateFee(l.ChanID(), feePerKw)
|
|
return l.cfg.Peer.SendMessage(msg)
|
|
}
|
|
|
|
// processLockedInHtlcs serially processes each of the log updates which have
|
|
// been "locked-in". An HTLC is considered locked-in once it has been fully
|
|
// committed to in both the remote and local commitment state. Once a channel
|
|
// updates is locked-in, then it can be acted upon, meaning: settling htlc's,
|
|
// cancelling them, or forwarding new HTLC's to the next hop.
|
|
func (l *channelLink) processLockedInHtlcs(
|
|
paymentDescriptors []*lnwallet.PaymentDescriptor) []*htlcPacket {
|
|
|
|
var (
|
|
needUpdate bool
|
|
packetsToForward []*htlcPacket
|
|
)
|
|
|
|
for _, pd := range paymentDescriptors {
|
|
// TODO(roasbeef): rework log entries to a shared
|
|
// interface.
|
|
switch pd.EntryType {
|
|
|
|
// A settle for an HTLC we previously forwarded HTLC has been
|
|
// received. So we'll forward the HTLC to the switch which
|
|
// will handle propagating the settle to the prior hop.
|
|
case lnwallet.Settle:
|
|
settleUpdate := &lnwire.UpdateFufillHTLC{
|
|
PaymentPreimage: pd.RPreimage,
|
|
}
|
|
settlePacket := newSettlePacket(l.ShortChanID(),
|
|
settleUpdate, pd.RHash, pd.Amount)
|
|
|
|
// Add the packet to the batch to be forwarded, and
|
|
// notify the overflow queue that a spare spot has been
|
|
// freed up within the commitment state.
|
|
packetsToForward = append(packetsToForward, settlePacket)
|
|
l.overflowQueue.SignalFreeSlot()
|
|
|
|
// A failureCode message for a previously forwarded HTLC has been
|
|
// received. As a result a new slot will be freed up in our
|
|
// commitment state, so we'll forward this to the switch so the
|
|
// backwards undo can continue.
|
|
case lnwallet.Fail:
|
|
// Fetch the reason the HTLC was cancelled so we can
|
|
// continue to propagate it.
|
|
opaqueReason := l.cancelReasons[pd.ParentIndex]
|
|
|
|
failUpdate := &lnwire.UpdateFailHTLC{
|
|
Reason: opaqueReason,
|
|
ChanID: l.ChanID(),
|
|
}
|
|
failPacket := newFailPacket(l.ShortChanID(), failUpdate,
|
|
pd.RHash, pd.Amount, false)
|
|
|
|
// Add the packet to the batch to be forwarded, and
|
|
// notify the overflow queue that a spare spot has been
|
|
// freed up within the commitment state.
|
|
packetsToForward = append(packetsToForward, failPacket)
|
|
l.overflowQueue.SignalFreeSlot()
|
|
|
|
// An incoming HTLC add has been full-locked in. As a result we
|
|
// can no examine the forwarding details of the HTLC, and the
|
|
// HTLC itself to decide if: we should forward it, cancel it,
|
|
// or are able to settle it (and it adheres to our fee related
|
|
// constraints).
|
|
case lnwallet.Add:
|
|
// Fetch the onion blob that was included within this
|
|
// processed payment descriptor.
|
|
onionBlob := l.clearedOnionBlobs[pd.HtlcIndex]
|
|
delete(l.clearedOnionBlobs, pd.HtlcIndex)
|
|
|
|
// Retrieve onion obfuscator from onion blob in order
|
|
// to produce initial obfuscation of the onion
|
|
// failureCode.
|
|
onionReader := bytes.NewReader(onionBlob[:])
|
|
obfuscator, failureCode := l.cfg.DecodeOnionObfuscator(
|
|
onionReader,
|
|
)
|
|
if failureCode != lnwire.CodeNone {
|
|
// If we're unable to process the onion blob
|
|
// than we should send the malformed htlc error
|
|
// to payment sender.
|
|
l.sendMalformedHTLCError(pd.RHash, failureCode,
|
|
onionBlob[:])
|
|
needUpdate = true
|
|
|
|
log.Errorf("unable to decode onion "+
|
|
"obfuscator: %v", failureCode)
|
|
continue
|
|
}
|
|
|
|
// Before adding the new htlc to the state machine,
|
|
// parse the onion object in order to obtain the
|
|
// routing information with DecodeHopIterator function
|
|
// which 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.
|
|
onionReader = bytes.NewReader(onionBlob[:])
|
|
chanIterator, failureCode := l.cfg.DecodeHopIterator(
|
|
onionReader, pd.RHash[:],
|
|
)
|
|
if failureCode != lnwire.CodeNone {
|
|
// If we're unable to process the onion blob
|
|
// than we should send the malformed htlc error
|
|
// to payment sender.
|
|
l.sendMalformedHTLCError(pd.RHash, failureCode,
|
|
onionBlob[:])
|
|
needUpdate = true
|
|
|
|
log.Errorf("unable to decode onion hop "+
|
|
"iterator: %v", failureCode)
|
|
continue
|
|
}
|
|
|
|
heightNow := l.bestHeight
|
|
|
|
fwdInfo := chanIterator.ForwardingInstructions()
|
|
switch fwdInfo.NextHop {
|
|
case exitHop:
|
|
// First, we'll check the expiry of the HTLC
|
|
// itself against, the current block height. If
|
|
// the timeout is too soon, then we'll reject
|
|
// the HTLC.
|
|
if pd.Timeout-expiryGraceDelta <= heightNow {
|
|
log.Errorf("htlc(%x) has an expiry "+
|
|
"that's too soon: expiry=%v, "+
|
|
"best_height=%v", pd.RHash[:],
|
|
pd.Timeout, heightNow)
|
|
|
|
failure := lnwire.FailFinalIncorrectCltvExpiry{}
|
|
l.sendHTLCError(pd.RHash, &failure, obfuscator)
|
|
needUpdate = true
|
|
continue
|
|
}
|
|
|
|
// We're the designated payment destination.
|
|
// Therefore we attempt to see if we have an
|
|
// invoice locally which'll allow us to settle
|
|
// this htlc.
|
|
invoiceHash := chainhash.Hash(pd.RHash)
|
|
invoice, err := l.cfg.Registry.LookupInvoice(invoiceHash)
|
|
if err != nil {
|
|
log.Errorf("unable to query invoice registry: "+
|
|
" %v", err)
|
|
failure := lnwire.FailUnknownPaymentHash{}
|
|
l.sendHTLCError(pd.RHash, failure, obfuscator)
|
|
needUpdate = true
|
|
continue
|
|
}
|
|
|
|
// If we're not currently in debug mode, and
|
|
// the extended htlc doesn't meet the value
|
|
// requested, then we'll fail the htlc.
|
|
// Otherwise, we settle this htlc within our
|
|
// local state update log, then send the update
|
|
// entry to the remote party.
|
|
if !l.cfg.DebugHTLC && pd.Amount < invoice.Terms.Value {
|
|
log.Errorf("rejecting htlc due to incorrect "+
|
|
"amount: expected %v, received %v",
|
|
invoice.Terms.Value, pd.Amount)
|
|
failure := lnwire.FailIncorrectPaymentAmount{}
|
|
l.sendHTLCError(pd.RHash, failure, obfuscator)
|
|
needUpdate = true
|
|
continue
|
|
}
|
|
|
|
// As we're the exit hop, we'll double check
|
|
// the hop-payload included in the HTLC to
|
|
// ensure that it was crafted correctly by the
|
|
// sender and matches the HTLC we were
|
|
// extended.
|
|
if !l.cfg.DebugHTLC &&
|
|
fwdInfo.AmountToForward != invoice.Terms.Value {
|
|
|
|
log.Errorf("Onion payload of incoming "+
|
|
"htlc(%x) has incorrect value: "+
|
|
"expected %v, got %v", pd.RHash,
|
|
invoice.Terms.Value,
|
|
fwdInfo.AmountToForward)
|
|
|
|
failure := lnwire.FailIncorrectPaymentAmount{}
|
|
l.sendHTLCError(pd.RHash, failure, obfuscator)
|
|
needUpdate = true
|
|
continue
|
|
}
|
|
|
|
// We'll also ensure that our time-lock value
|
|
// has been computed correctly.
|
|
//
|
|
// TODO(roasbeef): also accept global default?
|
|
expectedHeight := heightNow + l.cfg.FwrdingPolicy.TimeLockDelta
|
|
if !l.cfg.DebugHTLC {
|
|
switch {
|
|
case fwdInfo.OutgoingCTLV < expectedHeight:
|
|
log.Errorf("Onion payload of incoming "+
|
|
"htlc(%x) has incorrect time-lock: "+
|
|
"expected %v, got %v",
|
|
pd.RHash[:], expectedHeight,
|
|
fwdInfo.OutgoingCTLV)
|
|
|
|
failure := lnwire.NewFinalIncorrectCltvExpiry(
|
|
fwdInfo.OutgoingCTLV,
|
|
)
|
|
l.sendHTLCError(pd.RHash, failure, obfuscator)
|
|
needUpdate = true
|
|
continue
|
|
case pd.Timeout != fwdInfo.OutgoingCTLV:
|
|
log.Errorf("HTLC(%x) has incorrect "+
|
|
"time-lock: expected %v, got %v",
|
|
pd.RHash[:], pd.Timeout,
|
|
fwdInfo.OutgoingCTLV)
|
|
|
|
failure := lnwire.NewFinalIncorrectCltvExpiry(
|
|
fwdInfo.OutgoingCTLV,
|
|
)
|
|
l.sendHTLCError(pd.RHash, failure, obfuscator)
|
|
needUpdate = true
|
|
continue
|
|
}
|
|
}
|
|
|
|
if l.cfg.DebugHTLC && l.cfg.HodlHTLC {
|
|
log.Warnf("hodl HTLC mode enabled, " +
|
|
"will not attempt to settle " +
|
|
"HTLC with sender")
|
|
continue
|
|
}
|
|
|
|
preimage := invoice.Terms.PaymentPreimage
|
|
logIndex, amt, err := l.channel.SettleHTLC(preimage)
|
|
if err != nil {
|
|
l.fail("unable to settle htlc: %v", err)
|
|
return nil
|
|
}
|
|
|
|
// Increment the available bandwidth as we've
|
|
// settled an HTLC extended by tbe remote
|
|
// party.
|
|
atomic.AddUint64(
|
|
&l.availableBandwidth, uint64(amt),
|
|
)
|
|
|
|
// Notify the invoiceRegistry of the invoices
|
|
// we just settled with this latest commitment
|
|
// update.
|
|
err = l.cfg.Registry.SettleInvoice(invoiceHash)
|
|
if err != nil {
|
|
l.fail("unable to settle invoice: %v", err)
|
|
return nil
|
|
}
|
|
|
|
// HTLC was successfully settled locally send
|
|
// notification about it remote peer.
|
|
l.cfg.Peer.SendMessage(&lnwire.UpdateFufillHTLC{
|
|
ChanID: l.ChanID(),
|
|
ID: logIndex,
|
|
PaymentPreimage: preimage,
|
|
})
|
|
needUpdate = true
|
|
|
|
// There are additional channels left within this
|
|
// route. So we'll verify that our forwarding
|
|
// constraints have been properly met by by this
|
|
// incoming HTLC.
|
|
default:
|
|
// We want to avoid forwarding an HTLC which
|
|
// will expire in the near future, so we'll
|
|
// reject an HTLC if its expiration time is too
|
|
// close to the current height.
|
|
timeDelta := l.cfg.FwrdingPolicy.TimeLockDelta
|
|
if pd.Timeout-timeDelta <= heightNow {
|
|
log.Errorf("htlc(%x) has an expiry "+
|
|
"that's too soon: outgoing_expiry=%v, "+
|
|
"best_height=%v", pd.RHash[:],
|
|
pd.Timeout-timeDelta, heightNow)
|
|
|
|
var failure lnwire.FailureMessage
|
|
update, err := l.cfg.GetLastChannelUpdate()
|
|
if err != nil {
|
|
failure = lnwire.NewTemporaryChannelFailure(nil)
|
|
} else {
|
|
failure = lnwire.NewExpiryTooSoon(*update)
|
|
}
|
|
|
|
l.sendHTLCError(pd.RHash, failure, obfuscator)
|
|
needUpdate = true
|
|
continue
|
|
}
|
|
|
|
// As our second sanity check, we'll ensure that
|
|
// the passed HTLC isn't too small. If so, then
|
|
// we'll cancel the HTLC directly.
|
|
if pd.Amount < l.cfg.FwrdingPolicy.MinHTLC {
|
|
log.Errorf("Incoming htlc(%x) is too "+
|
|
"small: min_htlc=%v, hltc_value=%v",
|
|
pd.RHash[:], l.cfg.FwrdingPolicy.MinHTLC,
|
|
pd.Amount)
|
|
|
|
// As part of the returned error, we'll
|
|
// send our latest routing policy so
|
|
// the sending node obtains the most up
|
|
// to date data.
|
|
var failure lnwire.FailureMessage
|
|
update, err := l.cfg.GetLastChannelUpdate()
|
|
if err != nil {
|
|
failure = lnwire.NewTemporaryChannelFailure(nil)
|
|
} else {
|
|
failure = lnwire.NewAmountBelowMinimum(
|
|
pd.Amount, *update)
|
|
}
|
|
|
|
l.sendHTLCError(pd.RHash, failure, obfuscator)
|
|
needUpdate = true
|
|
continue
|
|
}
|
|
|
|
// Next, using the amount of the incoming HTLC,
|
|
// we'll calculate the expected fee this
|
|
// incoming HTLC must carry in order to be
|
|
// accepted.
|
|
expectedFee := ExpectedFee(
|
|
l.cfg.FwrdingPolicy,
|
|
fwdInfo.AmountToForward,
|
|
)
|
|
|
|
// If the amount of the incoming HTLC, minus
|
|
// our expected fee isn't equal to the
|
|
// forwarding instructions, then either the
|
|
// values have been tampered with, or the send
|
|
// used incorrect/dated information to
|
|
// construct the forwarding information for
|
|
// this hop. In any case, we'll cancel this
|
|
// HTLC.
|
|
if pd.Amount-expectedFee < fwdInfo.AmountToForward {
|
|
log.Errorf("Incoming htlc(%x) has "+
|
|
"insufficient fee: expected "+
|
|
"%v, got %v", pd.RHash[:],
|
|
int64(expectedFee),
|
|
int64(pd.Amount-fwdInfo.AmountToForward))
|
|
|
|
// As part of the returned error, we'll
|
|
// send our latest routing policy so
|
|
// the sending node obtains the most up
|
|
// to date data.
|
|
var failure lnwire.FailureMessage
|
|
update, err := l.cfg.GetLastChannelUpdate()
|
|
if err != nil {
|
|
failure = lnwire.NewTemporaryChannelFailure(nil)
|
|
} else {
|
|
failure = lnwire.NewFeeInsufficient(pd.Amount,
|
|
*update)
|
|
}
|
|
|
|
l.sendHTLCError(pd.RHash, failure, obfuscator)
|
|
needUpdate = true
|
|
continue
|
|
}
|
|
|
|
// Finally, we'll ensure that the time-lock on
|
|
// the outgoing HTLC meets the following
|
|
// constraint: the incoming time-lock minus our
|
|
// time-lock delta should equal the outgoing
|
|
// time lock. Otherwise, whether the sender
|
|
// messed up, or an intermediate node tampered
|
|
// with the HTLC.
|
|
if pd.Timeout-timeDelta < fwdInfo.OutgoingCTLV {
|
|
log.Errorf("Incoming htlc(%x) has "+
|
|
"incorrect time-lock value: "+
|
|
"expected at least %v block delta, "+
|
|
"got %v block delta", pd.RHash[:],
|
|
timeDelta,
|
|
pd.Timeout-fwdInfo.OutgoingCTLV)
|
|
|
|
// Grab the latest routing policy so
|
|
// the sending node is up to date with
|
|
// our current policy.
|
|
update, err := l.cfg.GetLastChannelUpdate()
|
|
if err != nil {
|
|
l.fail("unable to create channel update "+
|
|
"while handling the error: %v", err)
|
|
return nil
|
|
}
|
|
|
|
failure := lnwire.NewIncorrectCltvExpiry(
|
|
pd.Timeout, *update)
|
|
l.sendHTLCError(pd.RHash, failure, obfuscator)
|
|
needUpdate = true
|
|
continue
|
|
}
|
|
|
|
// TODO(roasbeef): also add max timeout value
|
|
|
|
// With all our forwarding constraints met,
|
|
// we'll create the outgoing HTLC using the
|
|
// parameters as specified in the forwarding
|
|
// info.
|
|
addMsg := &lnwire.UpdateAddHTLC{
|
|
Expiry: fwdInfo.OutgoingCTLV,
|
|
Amount: fwdInfo.AmountToForward,
|
|
PaymentHash: pd.RHash,
|
|
}
|
|
|
|
// Finally, we'll encode the onion packet for
|
|
// the _next_ hop using the hop iterator
|
|
// decoded for the current hop.
|
|
buf := bytes.NewBuffer(addMsg.OnionBlob[0:0])
|
|
err := chanIterator.EncodeNextHop(buf)
|
|
if err != nil {
|
|
log.Errorf("unable to encode the "+
|
|
"remaining route %v", err)
|
|
|
|
failure := lnwire.NewTemporaryChannelFailure(nil)
|
|
l.sendHTLCError(pd.RHash, failure, obfuscator)
|
|
needUpdate = true
|
|
continue
|
|
}
|
|
|
|
updatePacket := newAddPacket(l.ShortChanID(),
|
|
fwdInfo.NextHop, addMsg, obfuscator)
|
|
packetsToForward = append(packetsToForward, updatePacket)
|
|
}
|
|
}
|
|
}
|
|
|
|
if needUpdate {
|
|
// With all the settle/cancel updates added to the local and
|
|
// remote HTLC logs, initiate a state transition by updating
|
|
// the remote commitment chain.
|
|
if err := l.updateCommitTx(); err != nil {
|
|
l.fail("unable to update commitment: %v", err)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return packetsToForward
|
|
}
|
|
|
|
// sendHTLCError functions cancels HTLC and send cancel message back to the
|
|
// peer from which HTLC was received.
|
|
func (l *channelLink) sendHTLCError(rHash [32]byte, failure lnwire.FailureMessage,
|
|
e ErrorEncrypter) {
|
|
|
|
reason, err := e.EncryptFirstHop(failure)
|
|
if err != nil {
|
|
log.Errorf("unable to obfuscate error: %v", err)
|
|
return
|
|
}
|
|
|
|
index, err := l.channel.FailHTLC(rHash)
|
|
if err != nil {
|
|
log.Errorf("unable cancel htlc: %v", err)
|
|
return
|
|
}
|
|
|
|
l.cfg.Peer.SendMessage(&lnwire.UpdateFailHTLC{
|
|
ChanID: l.ChanID(),
|
|
ID: index,
|
|
Reason: reason,
|
|
})
|
|
}
|
|
|
|
// sendMalformedHTLCError helper function which sends the malformed HTLC update
|
|
// to the payment sender.
|
|
func (l *channelLink) sendMalformedHTLCError(rHash [32]byte, code lnwire.FailCode,
|
|
onionBlob []byte) {
|
|
index, err := l.channel.FailHTLC(rHash)
|
|
if err != nil {
|
|
log.Errorf("unable cancel htlc: %v", err)
|
|
return
|
|
}
|
|
|
|
l.cfg.Peer.SendMessage(&lnwire.UpdateFailMalformedHTLC{
|
|
ChanID: l.ChanID(),
|
|
ID: index,
|
|
ShaOnionBlob: sha256.Sum256(onionBlob),
|
|
FailureCode: code,
|
|
})
|
|
}
|
|
|
|
// fail helper function which is used to encapsulate the action necessary for
|
|
// proper disconnect.
|
|
func (l *channelLink) fail(format string, a ...interface{}) {
|
|
reason := errors.Errorf(format, a...)
|
|
log.Error(reason)
|
|
l.cfg.Peer.Disconnect(reason)
|
|
}
|