899fa1ea3e
This commit modifies the closeObserver code to populate the signDesc in the case we have a non-trimmed balance. Additionally, we now also add a *wire.OutPoint field to the struct in order to allow receivers of the message to construct a witness that can spend the newly created output to their wallet.
3935 lines
136 KiB
Go
3935 lines
136 KiB
Go
package lnwallet
|
|
|
|
import (
|
|
"bytes"
|
|
"container/list"
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"runtime"
|
|
"sort"
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
"github.com/davecgh/go-spew/spew"
|
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
|
"github.com/lightningnetwork/lnd/channeldb"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
"github.com/roasbeef/btcd/blockchain"
|
|
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
|
|
|
"encoding/hex"
|
|
|
|
"github.com/roasbeef/btcd/btcec"
|
|
"github.com/roasbeef/btcd/txscript"
|
|
"github.com/roasbeef/btcd/wire"
|
|
"github.com/roasbeef/btcutil"
|
|
"github.com/roasbeef/btcutil/txsort"
|
|
)
|
|
|
|
var zeroHash chainhash.Hash
|
|
|
|
var (
|
|
// ErrChanClosing is returned when a caller attempts to close a channel
|
|
// that has already been closed or is in the process of being closed.
|
|
ErrChanClosing = fmt.Errorf("channel is being closed, operation disallowed")
|
|
|
|
// ErrNoWindow is returned when revocation window is exausted.
|
|
ErrNoWindow = fmt.Errorf("unable to sign new commitment, the current" +
|
|
" revocation window is exhausted")
|
|
|
|
// ErrMaxWeightCost is returned when the cost/weight (see segwit)
|
|
// exceeds the widely used maximum allowed policy weight limit. In this
|
|
// case the commitment transaction can't be propagated through the
|
|
// network.
|
|
ErrMaxWeightCost = fmt.Errorf("commitment transaction exceed max " +
|
|
"available cost")
|
|
|
|
// ErrMaxHTLCNumber is returned when a proposed HTLC would exceed the
|
|
// maximum number of allowed HTLC's if committed in a state transition
|
|
ErrMaxHTLCNumber = fmt.Errorf("commitment transaction exceed max " +
|
|
"htlc number")
|
|
|
|
// ErrInsufficientBalance is returned when a proposed HTLC would
|
|
// exceed the available balance.
|
|
ErrInsufficientBalance = fmt.Errorf("insufficient local balance")
|
|
)
|
|
|
|
// channelState is an enum like type which represents the current state of a
|
|
// particular channel.
|
|
// TODO(roasbeef): actually update state
|
|
type channelState uint8
|
|
|
|
const (
|
|
// channelPending indicates this channel is still going through the
|
|
// funding workflow, and isn't yet open.
|
|
channelPending channelState = iota
|
|
|
|
// channelOpen represents an open, active channel capable of
|
|
// sending/receiving HTLCs.
|
|
channelOpen
|
|
|
|
// channelClosing represents a channel which is in the process of being
|
|
// closed.
|
|
channelClosing
|
|
|
|
// channelClosed represents a channel which has been fully closed. Note
|
|
// that before a channel can be closed, ALL pending HTLCs must be
|
|
// settled/removed.
|
|
channelClosed
|
|
|
|
// channelDispute indicates that an un-cooperative closure has been
|
|
// detected within the channel.
|
|
channelDispute
|
|
|
|
// channelPendingPayment indicates that there a currently outstanding
|
|
// HTLCs within the channel.
|
|
channelPendingPayment
|
|
)
|
|
|
|
// PaymentHash represents the sha256 of a random value. This hash is used to
|
|
// uniquely track incoming/outgoing payments within this channel, as well as
|
|
// payments requested by the wallet/daemon.
|
|
type PaymentHash [32]byte
|
|
|
|
// UpdateType is the exact type of an entry within the shared HTLC log.
|
|
type updateType uint8
|
|
|
|
const (
|
|
// Add is an update type that adds a new HTLC entry into the log.
|
|
// Either side can add a new pending HTLC by adding a new Add entry
|
|
// into their update log.
|
|
Add updateType = iota
|
|
|
|
// Fail is an update type which removes a prior HTLC entry from the
|
|
// log. Adding a Fail entry to ones log will modify the _remote_
|
|
// parties update log once a new commitment view has been evaluated
|
|
// which contains the Fail entry.
|
|
Fail
|
|
|
|
// Settle is an update type which settles a prior HTLC crediting the
|
|
// balance of the receiving node. Adding a Settle entry to a log will
|
|
// result in the settle entry being removed on the log as well as the
|
|
// original add entry from the remote party's log after the next state
|
|
// transition.
|
|
Settle
|
|
)
|
|
|
|
// PaymentDescriptor represents a commitment state update which either adds,
|
|
// settles, or removes an HTLC. PaymentDescriptors encapsulate all necessary
|
|
// metadata w.r.t to an HTLC, and additional data pairing a settle message to
|
|
// the original added HTLC.
|
|
//
|
|
// TODO(roasbeef): LogEntry interface??
|
|
// * need to separate attrs for cancel/add/settle
|
|
type PaymentDescriptor struct {
|
|
// RHash is the payment hash for this HTLC. The HTLC can be settled iff
|
|
// the preimage to this hash is presented.
|
|
RHash PaymentHash
|
|
|
|
// RPreimage is the preimage that settles the HTLC pointed to wthin the
|
|
// log by the ParentIndex.
|
|
RPreimage PaymentHash
|
|
|
|
// Timeout is the absolute timeout in blocks, after which this HTLC
|
|
// expires.
|
|
Timeout uint32
|
|
|
|
// Amount is the HTLC amount in satoshis.
|
|
Amount btcutil.Amount
|
|
|
|
// Index is the log entry number that his HTLC update has within the
|
|
// log. Depending on if IsIncoming is true, this is either an entry the
|
|
// remote party added, or one that we added locally.
|
|
Index uint64
|
|
|
|
// ParentIndex is the index of the log entry that this HTLC update
|
|
// settles or times out.
|
|
ParentIndex uint64
|
|
|
|
// localOutputIndex is the output index of this HTLc output in the
|
|
// commitment transaction of the local node.
|
|
//
|
|
// NOTE: If the output is dust from the PoV of the local comimtnet
|
|
// chain, then this value will be -1.
|
|
localOutputIndex int32
|
|
|
|
// remoteOutputIndex is the output index of this HTLc output in the
|
|
// commitment transaction of the remote node.
|
|
//
|
|
// NOTE: If the output is dust from the PoV of the remote commitment
|
|
// chain, then this value will be -1.
|
|
remoteOutputIndex int32
|
|
|
|
// sig is the signature for the second-level HTLC transaction that
|
|
// spends the version of this HTLC on the commitment transaction of the
|
|
// local node. This signature is generated by the remote node and
|
|
// stored by the local node in the case that local node needs to
|
|
// broadcast their commitment transaction.
|
|
sig *btcec.Signature
|
|
|
|
// addCommitHeight[Remote|Local] encodes the height of the commitment
|
|
// which included this HTLC on either the remote or local commitment
|
|
// chain. This value is used to determine when an HTLC is fully
|
|
// "locked-in".
|
|
addCommitHeightRemote uint64
|
|
addCommitHeightLocal uint64
|
|
|
|
// removeCommitHeight[Remote|Local] encodes the height of the
|
|
// commitment which removed the parent pointer of this
|
|
// PaymentDescriptor either due to a timeout or a settle. Once both
|
|
// these heights are above the tail of both chains, the log entries can
|
|
// safely be removed.
|
|
removeCommitHeightRemote uint64
|
|
removeCommitHeightLocal uint64
|
|
|
|
// Payload is an opaque blob which is used to complete multi-hop
|
|
// routing.
|
|
Payload []byte
|
|
|
|
// [our|their|]PkScript are the raw public key scripts that encodes the
|
|
// redemption rules for this particular HTLC. These fields will only be
|
|
// populated iff the EntryType of this PaymentDescriptor is Add.
|
|
// ourPkScript is the ourPkScript from the context of our local
|
|
// commitment chain. theirPkScript is the latest pkScript from the
|
|
// context of the remote commitment chain.
|
|
//
|
|
// NOTE: These values may change within the logs themselves, however,
|
|
// they'll stay consistent within the commitment chain entries
|
|
// themselves.
|
|
ourPkScript []byte
|
|
ourWitnessScript []byte
|
|
theirPkScript []byte
|
|
theirWitnessScript []byte
|
|
|
|
// EntryType denotes the exact type of the PaymentDescriptor. In the
|
|
// case of a Timeout, or Settle type, then the Parent field will point
|
|
// into the log to the HTLC being modified.
|
|
EntryType updateType
|
|
|
|
// isForwarded denotes if an incoming HTLC has been forwarded to any
|
|
// possible upstream peers in the route.
|
|
isForwarded bool
|
|
}
|
|
|
|
// commitment represents a commitment to a new state within an active channel.
|
|
// New commitments can be initiated by either side. Commitments are ordered
|
|
// into a commitment chain, with one existing for both parties. Each side can
|
|
// independently extend the other side's commitment chain, up to a certain
|
|
// "revocation window", which once reached, disallows new commitments until
|
|
// the local nodes receives the revocation for the remote node's chain tail.
|
|
type commitment struct {
|
|
// height represents the commitment height of this commitment, or the
|
|
// update number of this commitment.
|
|
height uint64
|
|
|
|
// [our|their]MessageIndex are indexes into the HTLC log, up to which
|
|
// this commitment transaction includes. These indexes allow both sides
|
|
// to independently, and concurrent send create new commitments. Each
|
|
// new commitment sent to the remote party includes an index in the
|
|
// shared log which details which of their updates we're including in
|
|
// this new commitment.
|
|
ourMessageIndex uint64
|
|
theirMessageIndex uint64
|
|
|
|
// txn is the commitment transaction generated by including any HTLC
|
|
// updates whose index are below the two indexes listed above. If this
|
|
// commitment is being added to the remote chain, then this txn is
|
|
// their version of the commitment transactions. If the local commit
|
|
// chain is being modified, the opposite is true.
|
|
txn *wire.MsgTx
|
|
|
|
// sig is a signature for the above commitment transaction.
|
|
sig []byte
|
|
|
|
// [our|their]Balance represents the settled balances at this point
|
|
// within the commitment chain. This balance is computed by properly
|
|
// evaluating all the add/remove/settle log entries before the listed
|
|
// indexes.
|
|
ourBalance btcutil.Amount
|
|
theirBalance btcutil.Amount
|
|
|
|
// fee is the amount that will be paid as fees for this commitment
|
|
// transaction. The fee is recorded here so that it can be added
|
|
// back and recalculated for each new update to the channel state.
|
|
fee btcutil.Amount
|
|
|
|
// feePerKw is the fee per kw used to calculate this commitment
|
|
// transaction's fee.
|
|
feePerKw btcutil.Amount
|
|
|
|
// outgoingHTLCs is a slice of all the outgoing HTLC's (from our PoV)
|
|
// on this commitment transaction.
|
|
outgoingHTLCs []PaymentDescriptor
|
|
|
|
// incomingHTLCs is a slice of all the incoming HTLC's (from our PoV)
|
|
// on this commitment transaction.
|
|
incomingHTLCs []PaymentDescriptor
|
|
|
|
// [outgoing|incoming]HTLCIndex is an index that maps an output index
|
|
// on the commitment transaction to the payment descriptor that
|
|
// represents the HTLC output. Note that these fields are only
|
|
// populated if this commitment state belongs to the local node. These
|
|
// maps are used when validating any HTLC signatures which are part of
|
|
// the local commitment state. We use this map in order to locate the
|
|
// details needed to validate an HTLC signature while iterating of the
|
|
// outputs int he local commitment view.
|
|
outgoignHTLCIndex map[int32]*PaymentDescriptor
|
|
incomingHTLCIndex map[int32]*PaymentDescriptor
|
|
}
|
|
|
|
// locateOutputIndex is a small helper function to locate the output index of a
|
|
// particular HTLC within the current commitment transaction. The duplicate map
|
|
// massed in is to be retained for each output within the commitment
|
|
// transition. This ensures that we don't assign multiple HTLC's to the same
|
|
// index within the commitment transaction.
|
|
func locateOutputIndex(p *PaymentDescriptor, tx *wire.MsgTx, ourCommit bool,
|
|
dups map[PaymentHash][]int32) (int32, error) {
|
|
|
|
// Checks to see if element (e) exists in slice (s).
|
|
contains := func(s []int32, e int32) bool {
|
|
for _, a := range s {
|
|
if a == e {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// If this their commitment transaction, we'll be trying to locate
|
|
// their pkScripts, otherwise we'll be looking for ours. This is
|
|
// required as the commitment states are asymmetric in order to ascribe
|
|
// blame in the case of a contract breach.
|
|
pkScript := p.theirPkScript
|
|
if ourCommit {
|
|
pkScript = p.ourPkScript
|
|
}
|
|
|
|
for i, txOut := range tx.TxOut {
|
|
if bytes.Equal(txOut.PkScript, pkScript) &&
|
|
txOut.Value == int64(p.Amount) {
|
|
|
|
// If this payment hash and index has already been
|
|
// found, then we'll continue in order to avoid any
|
|
// duplicate indexes.
|
|
if contains(dups[p.RHash], int32(i)) {
|
|
continue
|
|
}
|
|
|
|
idx := int32(i)
|
|
dups[p.RHash] = append(dups[p.RHash], idx)
|
|
return idx, nil
|
|
}
|
|
}
|
|
|
|
return 0, fmt.Errorf("unable to find htlc: script=%x, value=%v",
|
|
pkScript, p.Amount)
|
|
}
|
|
|
|
// populateHtlcIndexes modifies the set of HTLC's locked-into the target view
|
|
// to have full indexing information populated. This information is required as
|
|
// we need to keep track of the indexes of each HTLC in order to properly write
|
|
// the current state to disk, and also to locate the PaymentDescriptor
|
|
// corresponding to HTLC outputs in the commitment transaction.
|
|
func (c *commitment) populateHtlcIndexes(ourCommitTx bool,
|
|
dustLimit btcutil.Amount) error {
|
|
|
|
// First, we'll set up some state to allow us to locate the output
|
|
// index of the all the HTLC's within the commitment transaction. We
|
|
// must keep this index so we can validate the HTLC signatures sent to
|
|
// us.
|
|
dups := make(map[PaymentHash][]int32)
|
|
c.outgoignHTLCIndex = make(map[int32]*PaymentDescriptor)
|
|
c.incomingHTLCIndex = make(map[int32]*PaymentDescriptor)
|
|
|
|
// populateIndex is a helper function that populates the necessary
|
|
// indexes within the commitment view for a particular HTLC.
|
|
populateIndex := func(htlc *PaymentDescriptor, incoming bool) error {
|
|
isDust := htlcIsDust(incoming, ourCommitTx, c.feePerKw,
|
|
htlc.Amount, dustLimit)
|
|
|
|
var err error
|
|
switch {
|
|
|
|
// If this is our commitment transaction, and this is a dust
|
|
// output then we mark it as such using a -1 index.
|
|
case ourCommitTx && isDust:
|
|
htlc.localOutputIndex = -1
|
|
|
|
// If this is the commitment transaction of the remote party,
|
|
// and this is a dust output then we mark it as such using a -1
|
|
// index.
|
|
case !ourCommitTx && isDust:
|
|
htlc.remoteOutputIndex = -1
|
|
|
|
// If this is our commitment transaction, then we'll need to
|
|
// locate the output and the index so we can verify an HTLC
|
|
// signatures.
|
|
case ourCommitTx:
|
|
htlc.localOutputIndex, err = locateOutputIndex(htlc, c.txn,
|
|
ourCommitTx, dups)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// As this is our commitment transactions, we need to
|
|
// keep track of the locations of each output on the
|
|
// transaction so we can verify any HTLC signatures
|
|
// sent to us after we construct the HTLC view.
|
|
if incoming {
|
|
c.incomingHTLCIndex[htlc.localOutputIndex] = htlc
|
|
} else {
|
|
c.outgoignHTLCIndex[htlc.localOutputIndex] = htlc
|
|
}
|
|
|
|
// Otherwise, this is there remote party's commitment
|
|
// transaction and we only need to populate the remote output
|
|
// index within the HTLC index.
|
|
case !ourCommitTx:
|
|
htlc.remoteOutputIndex, err = locateOutputIndex(htlc, c.txn,
|
|
ourCommitTx, dups)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
default:
|
|
return fmt.Errorf("invalid commitment configuration")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Finally, we'll need to locate the index within the commitment
|
|
// transaction of all the HTLC outputs. This index will be required
|
|
// later when we write the commitment state to disk, and also when
|
|
// generating signatures for each of the HTLC transactions.
|
|
for i := 0; i < len(c.outgoingHTLCs); i++ {
|
|
htlc := &c.outgoingHTLCs[i]
|
|
if err := populateIndex(htlc, false); err != nil {
|
|
return nil
|
|
}
|
|
}
|
|
for i := 0; i < len(c.incomingHTLCs); i++ {
|
|
htlc := &c.incomingHTLCs[i]
|
|
if err := populateIndex(htlc, true); err != nil {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// toChannelDelta converts the target commitment into a format suitable to be
|
|
// written to disk after an accepted state transition.
|
|
func (c *commitment) toChannelDelta(ourCommit bool) (*channeldb.ChannelDelta, error) {
|
|
numHtlcs := len(c.outgoingHTLCs) + len(c.incomingHTLCs)
|
|
|
|
delta := &channeldb.ChannelDelta{
|
|
LocalBalance: c.ourBalance,
|
|
RemoteBalance: c.theirBalance,
|
|
UpdateNum: c.height,
|
|
CommitFee: c.fee,
|
|
FeePerKw: c.feePerKw,
|
|
Htlcs: make([]*channeldb.HTLC, 0, numHtlcs),
|
|
}
|
|
|
|
for _, htlc := range c.outgoingHTLCs {
|
|
outputIndex := htlc.localOutputIndex
|
|
if !ourCommit {
|
|
outputIndex = htlc.remoteOutputIndex
|
|
}
|
|
|
|
h := &channeldb.HTLC{
|
|
Incoming: false,
|
|
Amt: htlc.Amount,
|
|
RHash: htlc.RHash,
|
|
RefundTimeout: htlc.Timeout,
|
|
OutputIndex: outputIndex,
|
|
}
|
|
|
|
if ourCommit && htlc.sig != nil {
|
|
h.Signature = htlc.sig.Serialize()
|
|
}
|
|
|
|
delta.Htlcs = append(delta.Htlcs, h)
|
|
}
|
|
|
|
for _, htlc := range c.incomingHTLCs {
|
|
outputIndex := htlc.localOutputIndex
|
|
if !ourCommit {
|
|
outputIndex = htlc.remoteOutputIndex
|
|
}
|
|
|
|
h := &channeldb.HTLC{
|
|
Incoming: true,
|
|
Amt: htlc.Amount,
|
|
RHash: htlc.RHash,
|
|
RefundTimeout: htlc.Timeout,
|
|
OutputIndex: outputIndex,
|
|
}
|
|
|
|
if ourCommit && htlc.sig != nil {
|
|
h.Signature = htlc.sig.Serialize()
|
|
}
|
|
|
|
delta.Htlcs = append(delta.Htlcs, h)
|
|
}
|
|
|
|
return delta, nil
|
|
}
|
|
|
|
// commitmentChain represents a chain of unrevoked commitments. The tail of the
|
|
// chain is the latest fully signed, yet unrevoked commitment. Two chains are
|
|
// tracked, one for the local node, and another for the remote node. New
|
|
// commitments we create locally extend the remote node's chain, and vice
|
|
// versa. Commitment chains are allowed to grow to a bounded length, after
|
|
// which the tail needs to be "dropped" before new commitments can be received.
|
|
// The tail is "dropped" when the owner of the chain sends a revocation for the
|
|
// previous tail.
|
|
type commitmentChain struct {
|
|
// commitments is a linked list of commitments to new states. New
|
|
// commitments are added to the end of the chain with increase height.
|
|
// Once a commitment transaction is revoked, the tail is incremented,
|
|
// freeing up the revocation window for new commitments.
|
|
commitments *list.List
|
|
|
|
// startingHeight is the starting height of this commitment chain on a
|
|
// session basis.
|
|
startingHeight uint64
|
|
}
|
|
|
|
// newCommitmentChain creates a new commitment chain from an initial height.
|
|
func newCommitmentChain(initialHeight uint64) *commitmentChain {
|
|
return &commitmentChain{
|
|
commitments: list.New(),
|
|
startingHeight: initialHeight,
|
|
}
|
|
}
|
|
|
|
// addCommitment extends the commitment chain by a single commitment. This
|
|
// added commitment represents a state update propsed by either party. Once the
|
|
// commitment prior to this commitment is revoked, the commitment becomes the
|
|
// new defacto state within the channel.
|
|
func (s *commitmentChain) addCommitment(c *commitment) {
|
|
s.commitments.PushBack(c)
|
|
}
|
|
|
|
// advanceTail reduces the length of the commitment chain by one. The tail of
|
|
// the chain should be advanced once a revocation for the lowest unrevoked
|
|
// commitment in the chain is received.
|
|
func (s *commitmentChain) advanceTail() {
|
|
s.commitments.Remove(s.commitments.Front())
|
|
}
|
|
|
|
// tip returns the latest commitment added to the chain.
|
|
func (s *commitmentChain) tip() *commitment {
|
|
return s.commitments.Back().Value.(*commitment)
|
|
}
|
|
|
|
// tail returns the lowest unrevoked commitment transaction in the chain.
|
|
func (s *commitmentChain) tail() *commitment {
|
|
return s.commitments.Front().Value.(*commitment)
|
|
}
|
|
|
|
// updateLog is an append-only log that stores updates to a node's commitment
|
|
// chain. This structure can be seen as the "mempool" within Lightning where
|
|
// changes are stored before they're committed to the chain. Once an entry has
|
|
// been committed in both the local and remote commitment chain, then it can be
|
|
// removed from this log.
|
|
//
|
|
// TODO(roasbeef): create lightning package, move commitment and update to
|
|
// package?
|
|
// * also move state machine, separate from lnwallet package
|
|
// * possible embed updateLog within commitmentChain.
|
|
type updateLog struct {
|
|
// logIndex is a monotonically increasing integer that tracks the total
|
|
// number of update entries ever applied to the log. When sending new
|
|
// commitment states, we include all updates up to this index.
|
|
logIndex uint64
|
|
|
|
// ackIndex is a special "pointer" index into the log that tracks the
|
|
// position which, up to, all changes have been ACK'd by the remote
|
|
// party. When receiving new commitment states, we include all of our
|
|
// updates up to this index to restore the commitment view.
|
|
ackedIndex uint64
|
|
|
|
// pendingACKIndex is another special "pointer" index into the log that
|
|
// tracks our logIndex value right before we extend the remote party's
|
|
// commitment chain. Once we receive an ACK for this changes, then we
|
|
// set ackedIndex=pendingAckIndex.
|
|
//
|
|
// TODO(roasbeef): eventually expand into list when we go back to a
|
|
// sliding window format
|
|
pendingAckIndex uint64
|
|
|
|
// List is the updatelog itself, we embed this value so updateLog has
|
|
// access to all the method of a list.List.
|
|
*list.List
|
|
|
|
// updateIndex is an index that maps a particular entries index to the
|
|
// list element within the list.List above.
|
|
updateIndex map[uint64]*list.Element
|
|
}
|
|
|
|
// newUpdateLog creates a new updateLog instance.
|
|
func newUpdateLog() *updateLog {
|
|
return &updateLog{
|
|
List: list.New(),
|
|
updateIndex: make(map[uint64]*list.Element),
|
|
}
|
|
}
|
|
|
|
// appendUpdate appends a new update to the tip of the updateLog. The entry is
|
|
// also added to index accordingly.
|
|
func (u *updateLog) appendUpdate(pd *PaymentDescriptor) {
|
|
u.updateIndex[u.logIndex] = u.PushBack(pd)
|
|
u.logIndex++
|
|
}
|
|
|
|
// lookup attempts to look up an update entry according to it's index value. In
|
|
// the case that the entry isn't found, a nil pointer is returned.
|
|
func (u *updateLog) lookup(i uint64) *PaymentDescriptor {
|
|
return u.updateIndex[i].Value.(*PaymentDescriptor)
|
|
}
|
|
|
|
// remove attempts to remove an entry from the update log. If the entry is
|
|
// found, then the entry will be removed from the update log and index.
|
|
func (u *updateLog) remove(i uint64) {
|
|
entry := u.updateIndex[i]
|
|
u.Remove(entry)
|
|
delete(u.updateIndex, i)
|
|
}
|
|
|
|
// initiateTransition marks that the caller has extended the commitment chain
|
|
// of the remote party with the contents of the updateLog. This function will
|
|
// mark the log index value at this point so it can later be marked as ACK'd.
|
|
func (u *updateLog) initiateTransition() {
|
|
u.pendingAckIndex = u.logIndex
|
|
}
|
|
|
|
// ackTransition updates the internal indexes of the updateLog to mark that the
|
|
// last pending state transition has been accepted by the remote party. To do
|
|
// so, we mark the prior pendingAckIndex as fully ACK'd.
|
|
func (u *updateLog) ackTransition() {
|
|
u.ackedIndex = u.pendingAckIndex
|
|
u.pendingAckIndex = 0
|
|
}
|
|
|
|
// compactLogs performs garbage collection within the log removing HTLCs which
|
|
// have been removed from the point-of-view of the tail of both chains. The
|
|
// entries which timeout/settle HTLCs are also removed.
|
|
func compactLogs(ourLog, theirLog *updateLog,
|
|
localChainTail, remoteChainTail uint64) {
|
|
|
|
compactLog := func(logA, logB *updateLog) {
|
|
var nextA *list.Element
|
|
for e := logA.Front(); e != nil; e = nextA {
|
|
nextA = e.Next()
|
|
|
|
htlc := e.Value.(*PaymentDescriptor)
|
|
if htlc.EntryType == Add {
|
|
continue
|
|
}
|
|
|
|
// If the HTLC hasn't yet been removed from either
|
|
// chain, the skip it.
|
|
if htlc.removeCommitHeightRemote == 0 ||
|
|
htlc.removeCommitHeightLocal == 0 {
|
|
continue
|
|
}
|
|
|
|
// Otherwise if the height of the tail of both chains
|
|
// is at least the height in which the HTLC was
|
|
// removed, then evict the settle/timeout entry along
|
|
// with the original add entry.
|
|
if remoteChainTail >= htlc.removeCommitHeightRemote &&
|
|
localChainTail >= htlc.removeCommitHeightLocal {
|
|
|
|
logA.remove(htlc.Index)
|
|
logB.remove(htlc.ParentIndex)
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
compactLog(ourLog, theirLog)
|
|
|
|
compactLog(theirLog, ourLog)
|
|
}
|
|
|
|
// LightningChannel implements the state machine which corresponds to the
|
|
// current commitment protocol wire spec. The state machine implemented allows
|
|
// for asynchronous fully desynchronized, batched+pipelined updates to
|
|
// commitment transactions allowing for a high degree of non-blocking
|
|
// bi-directional payment throughput.
|
|
//
|
|
// In order to allow updates to be fully non-blocking, either side is able to
|
|
// create multiple new commitment states up to a pre-determined window size.
|
|
// This window size is encoded within InitialRevocationWindow. Before the start
|
|
// of a session, both side should send out revocation messages with nil
|
|
// preimages in order to populate their revocation window for the remote party.
|
|
// Ths method .ExtendRevocationWindow() is used to extend the revocation window
|
|
// by a single revocation.
|
|
//
|
|
// The state machine has for main methods:
|
|
// * .SignNextCommitment()
|
|
// * Called one one wishes to sign the next commitment, either initiating a
|
|
// new state update, or responding to a received commitment.
|
|
// * .ReceiveNewCommitment()
|
|
// * Called upon receipt of a new commitment from the remote party. If the
|
|
// new commitment is valid, then a revocation should immediately be
|
|
// generated and sent.
|
|
// * .RevokeCurrentCommitment()
|
|
// * Revokes the current commitment. Should be called directly after
|
|
// receiving a new commitment.
|
|
// * .ReceiveRevocation()
|
|
// * Processes a revocation from the remote party. If successful creates a
|
|
// new defacto broadcastable state.
|
|
//
|
|
// See the individual comments within the above methods for further details.
|
|
type LightningChannel struct {
|
|
// signer is the main signer instances that will be responsible for
|
|
// signing any HTLC and commitment transaction generated by the state
|
|
// machine.
|
|
signer Signer
|
|
|
|
// signDesc is the primary sign descriptor that is capable of signing
|
|
// the commitment transaction that spends the multi-sig output.
|
|
signDesc *SignDescriptor
|
|
|
|
channelEvents chainntnfs.ChainNotifier
|
|
|
|
// pendingACk denotes if we have an outstanding commitment transaction
|
|
// and are waiting for a revocation to be received. Until the
|
|
// revocation is received, we're unable to propose a new commitment
|
|
// state.
|
|
pendingACK bool
|
|
|
|
status channelState
|
|
|
|
// sigPool is a pool of workers that are capable of signing and
|
|
// validating signatures in parallel. This is utilized as an
|
|
// optimization to void serially signing or validating the HTLC
|
|
// signatures, of which there may be hundreds.
|
|
sigPool *sigPool
|
|
|
|
// feeEstimator is used to calculate the fee rate for the channel's
|
|
// commitment and cooperative close transactions.
|
|
feeEstimator FeeEstimator
|
|
|
|
// Capcity is the total capacity of this channel.
|
|
Capacity btcutil.Amount
|
|
|
|
// stateHintObsfucator is a 48-bit state hint that's used to obfsucate
|
|
// the current state number on the commitment transactions.
|
|
stateHintObsfucator [StateHintSize]byte
|
|
|
|
// currentHeight is the current height of our local commitment chain.
|
|
// This is also the same as the number of updates to the channel we've
|
|
// accepted.
|
|
currentHeight uint64
|
|
|
|
// remoteCommitChain is the remote node's commitment chain. Any new
|
|
// commitments we initiate are added to the tip of this chain.
|
|
remoteCommitChain *commitmentChain
|
|
|
|
// localCommitChain is our local commitment chain. Any new commitments
|
|
// received are added to the tip of this chain. The tail (or lowest
|
|
// height) in this chain is our current accepted state, which we are
|
|
// able to broadcast safely.
|
|
localCommitChain *commitmentChain
|
|
|
|
channelState *channeldb.OpenChannel
|
|
|
|
localChanCfg *channeldb.ChannelConfig
|
|
|
|
remoteChanCfg *channeldb.ChannelConfig
|
|
|
|
// [local|remote]Log is a (mostly) append-only log storing all the HTLC
|
|
// updates to this channel. The log is walked backwards as HTLC updates
|
|
// are applied in order to re-construct a commitment transaction from a
|
|
// commitment. The log is compacted once a revocation is received.
|
|
localUpdateLog *updateLog
|
|
remoteUpdateLog *updateLog
|
|
|
|
// pendingFeeUpdate is set to the fee-per-kw we last sent (if we are
|
|
// channel initiator) or received (if non-initiator) in an update fee
|
|
// message, which haven't yet been included in a commitment. It will
|
|
// be nil if no fee update is un-committed.
|
|
pendingFeeUpdate *btcutil.Amount
|
|
|
|
// pendingAckFeeUpdate is set to the last committed fee update which is
|
|
// not yet ACKed. This value will be nil if a fee update hasn't been
|
|
// initiated.
|
|
pendingAckFeeUpdate *btcutil.Amount
|
|
|
|
// rHashMap is a map with PaymentHashes pointing to their respective
|
|
// PaymentDescriptors. We insert *PaymentDescriptors whenever we
|
|
// receive HTLCs. When a state transition happens (settling or
|
|
// canceling the HTLC), rHashMap will provide an efficient
|
|
// way to lookup the original PaymentDescriptor.
|
|
rHashMap map[PaymentHash][]*PaymentDescriptor
|
|
|
|
// FundingWitnessScript is the witness script for the 2-of-2 multi-sig
|
|
// that opened the channel.
|
|
FundingWitnessScript []byte
|
|
|
|
fundingTxIn *wire.TxIn
|
|
fundingP2WSH []byte
|
|
|
|
// ForceCloseSignal is a channel that is closed to indicate that a
|
|
// local system has initiated a force close by broadcasting the current
|
|
// commitment transaction directly on-chain.
|
|
ForceCloseSignal chan struct{}
|
|
|
|
// UnilateralCloseSignal is a channel that is closed to indicate that
|
|
// the remote party has performed a unilateral close by broadcasting
|
|
// their version of the commitment transaction on-chain.
|
|
UnilateralCloseSignal chan struct{}
|
|
|
|
// UnilateralClose is a channel that will be sent upon by the close
|
|
// observer once the unilateral close of a channel is detected.
|
|
UnilateralClose chan *UnilateralCloseSummary
|
|
|
|
// ContractBreach is a channel that is used to communicate the data
|
|
// necessary to fully resolve the channel in the case that a contract
|
|
// breach is detected. A contract breach occurs it is detected that the
|
|
// counterparty has broadcast a prior *revoked* state.
|
|
ContractBreach chan *BreachRetribution
|
|
|
|
// LocalFundingKey is the public key under control by the wallet that
|
|
// was used for the 2-of-2 funding output which created this channel.
|
|
LocalFundingKey *btcec.PublicKey
|
|
|
|
// RemoteFundingKey is the public key for the remote channel counter
|
|
// party which used for the 2-of-2 funding output which created this
|
|
// channel.
|
|
RemoteFundingKey *btcec.PublicKey
|
|
|
|
// availableLocalBalance represent the amount of available money which
|
|
// might be processed by this channel at the specific point of time.
|
|
availableLocalBalance btcutil.Amount
|
|
|
|
sync.RWMutex
|
|
|
|
wg sync.WaitGroup
|
|
|
|
shutdown int32
|
|
quit chan struct{}
|
|
}
|
|
|
|
// NewLightningChannel creates a new, active payment channel given an
|
|
// implementation of the chain notifier, channel database, and the current
|
|
// settled channel state. Throughout state transitions, then channel will
|
|
// automatically persist pertinent state to the database in an efficient
|
|
// manner.
|
|
func NewLightningChannel(signer Signer, events chainntnfs.ChainNotifier,
|
|
fe FeeEstimator, state *channeldb.OpenChannel) (*LightningChannel, error) {
|
|
|
|
localKey := state.LocalChanCfg.MultiSigKey.SerializeCompressed()
|
|
remoteKey := state.RemoteChanCfg.MultiSigKey.SerializeCompressed()
|
|
multiSigScript, err := genMultiSigScript(localKey, remoteKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var stateHint [StateHintSize]byte
|
|
if state.IsInitiator {
|
|
stateHint = deriveStateHintObfuscator(
|
|
state.LocalChanCfg.PaymentBasePoint,
|
|
state.RemoteChanCfg.PaymentBasePoint,
|
|
)
|
|
} else {
|
|
stateHint = deriveStateHintObfuscator(
|
|
state.RemoteChanCfg.PaymentBasePoint,
|
|
state.LocalChanCfg.PaymentBasePoint,
|
|
)
|
|
}
|
|
|
|
lc := &LightningChannel{
|
|
// TODO(roasbeef): tune num sig workers?
|
|
sigPool: newSigPool(runtime.NumCPU(), signer),
|
|
signer: signer,
|
|
channelEvents: events,
|
|
feeEstimator: fe,
|
|
stateHintObsfucator: stateHint,
|
|
currentHeight: state.NumUpdates,
|
|
remoteCommitChain: newCommitmentChain(state.NumUpdates),
|
|
localCommitChain: newCommitmentChain(state.NumUpdates),
|
|
channelState: state,
|
|
localChanCfg: &state.LocalChanCfg,
|
|
remoteChanCfg: &state.RemoteChanCfg,
|
|
localUpdateLog: newUpdateLog(),
|
|
remoteUpdateLog: newUpdateLog(),
|
|
rHashMap: make(map[PaymentHash][]*PaymentDescriptor),
|
|
Capacity: state.Capacity,
|
|
FundingWitnessScript: multiSigScript,
|
|
ForceCloseSignal: make(chan struct{}),
|
|
UnilateralClose: make(chan *UnilateralCloseSummary, 1),
|
|
UnilateralCloseSignal: make(chan struct{}),
|
|
ContractBreach: make(chan *BreachRetribution, 1),
|
|
LocalFundingKey: state.LocalChanCfg.MultiSigKey,
|
|
RemoteFundingKey: state.RemoteChanCfg.MultiSigKey,
|
|
quit: make(chan struct{}),
|
|
}
|
|
|
|
// Initialize both of our chains using current un-revoked commitment
|
|
// for each side.
|
|
lc.localCommitChain.addCommitment(&commitment{
|
|
height: lc.currentHeight,
|
|
ourBalance: state.LocalBalance,
|
|
ourMessageIndex: 0,
|
|
theirBalance: state.RemoteBalance,
|
|
theirMessageIndex: 0,
|
|
fee: state.CommitFee,
|
|
feePerKw: state.FeePerKw,
|
|
})
|
|
walletLog.Debugf("ChannelPoint(%v), starting local commitment: %v",
|
|
state.FundingOutpoint, newLogClosure(func() string {
|
|
return spew.Sdump(lc.localCommitChain.tail())
|
|
}),
|
|
)
|
|
|
|
// To obtain the proper height for the remote node's commitment state,
|
|
// we'll need to fetch the tail end of their revocation log from the
|
|
// database.
|
|
logTail, err := state.RevocationLogTail()
|
|
if err != nil && err != channeldb.ErrNoActiveChannels &&
|
|
err != channeldb.ErrNoPastDeltas {
|
|
return nil, err
|
|
}
|
|
remoteCommitment := &commitment{
|
|
ourBalance: state.LocalBalance,
|
|
ourMessageIndex: 0,
|
|
theirBalance: state.RemoteBalance,
|
|
theirMessageIndex: 0,
|
|
fee: state.CommitFee,
|
|
feePerKw: state.FeePerKw,
|
|
}
|
|
if logTail == nil {
|
|
remoteCommitment.height = 0
|
|
} else {
|
|
remoteCommitment.height = logTail.UpdateNum + 1
|
|
}
|
|
lc.remoteCommitChain.addCommitment(remoteCommitment)
|
|
walletLog.Debugf("ChannelPoint(%v), starting remote commitment: %v",
|
|
state.FundingOutpoint, newLogClosure(func() string {
|
|
return spew.Sdump(lc.remoteCommitChain.tail())
|
|
}),
|
|
)
|
|
|
|
// If we're restarting from a channel with history, then restore the
|
|
// update in-memory update logs to that of the prior state.
|
|
if lc.currentHeight != 0 {
|
|
lc.restoreStateLogs()
|
|
}
|
|
|
|
// Create the sign descriptor which we'll be using very frequently to
|
|
// request a signature for the 2-of-2 multi-sig from the signer in
|
|
// order to complete channel state transitions.
|
|
fundingPkScript, err := witnessScriptHash(multiSigScript)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
lc.fundingTxIn = wire.NewTxIn(&state.FundingOutpoint, nil, nil)
|
|
lc.fundingP2WSH = fundingPkScript
|
|
lc.signDesc = &SignDescriptor{
|
|
PubKey: lc.localChanCfg.MultiSigKey,
|
|
WitnessScript: multiSigScript,
|
|
Output: &wire.TxOut{
|
|
PkScript: lc.fundingP2WSH,
|
|
Value: int64(lc.channelState.Capacity),
|
|
},
|
|
HashType: txscript.SigHashAll,
|
|
InputIndex: 0,
|
|
}
|
|
|
|
// We'll only launch a close observer if the ChainNotifier
|
|
// implementation is non-nil. Passing a nil value indicates that the
|
|
// channel shouldn't be actively watched for.
|
|
if lc.channelEvents != nil {
|
|
// Register for a notification to be dispatched if the funding
|
|
// outpoint has been spent. This indicates that either us or
|
|
// the remote party has broadcasted a commitment transaction
|
|
// on-chain.
|
|
fundingOut := &lc.fundingTxIn.PreviousOutPoint
|
|
|
|
// As a height hint, we'll try to use the opening height, but
|
|
// if the channel isn't yet open, then we'll use the height it
|
|
// was broadcast at.
|
|
heightHint := lc.channelState.ShortChanID.BlockHeight
|
|
if heightHint == 0 {
|
|
heightHint = lc.channelState.FundingBroadcastHeight
|
|
}
|
|
|
|
channelCloseNtfn, err := lc.channelEvents.RegisterSpendNtfn(
|
|
fundingOut, heightHint,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Launch the close observer which will vigilantly watch the
|
|
// network for any broadcasts the current or prior commitment
|
|
// transactions, taking action accordingly.
|
|
lc.wg.Add(1)
|
|
go lc.closeObserver(channelCloseNtfn)
|
|
}
|
|
|
|
// Initialize the available local balance
|
|
s := lc.StateSnapshot()
|
|
lc.availableLocalBalance = s.LocalBalance
|
|
|
|
// Finally, we'll kick of the signature job pool to handle any upcoming
|
|
// commitment state generation and validation.
|
|
if lc.sigPool.Start(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return lc, nil
|
|
}
|
|
|
|
// Stop gracefully shuts down any active goroutines spawned by the
|
|
// LightningChannel during regular duties.
|
|
func (lc *LightningChannel) Stop() {
|
|
if !atomic.CompareAndSwapInt32(&lc.shutdown, 0, 1) {
|
|
return
|
|
}
|
|
|
|
// TODO(roasbeef): ensure that when channel links and breach arbs exit,
|
|
// that they call Stop?
|
|
|
|
lc.sigPool.Stop()
|
|
|
|
close(lc.quit)
|
|
|
|
lc.wg.Wait()
|
|
}
|
|
|
|
// HtlcRetribution contains all the items necessary to seep a revoked HTLC
|
|
// transaction from a revoked commitment transaction broadcast by the remot
|
|
// party.
|
|
type HtlcRetribution struct {
|
|
// SignDesc is a design descriptor capable of generating the necessary
|
|
// signatures to satisfy the revocation clause of the HTLC's public key
|
|
// script.
|
|
SignDesc SignDescriptor
|
|
|
|
// OutPoint is the target outpoint of this HTLC pointing to the
|
|
// breached commitment transaction.
|
|
OutPoint wire.OutPoint
|
|
}
|
|
|
|
// BreachRetribution contains all the data necessary to bring a channel
|
|
// counterparty to justice claiming ALL lingering funds within the channel in
|
|
// the scenario that they broadcast a revoked commitment transaction. A
|
|
// BreachRetribution is created by the closeObserver if it detects an
|
|
// uncooperative close of the channel which uses a revoked commitment
|
|
// transaction. The BreachRetribution is then sent over the ContractBreach
|
|
// channel in order to allow the subscriber of the channel to dispatch justice.
|
|
type BreachRetribution struct {
|
|
// BreachTransaction is the transaction which breached the channel
|
|
// contract by spending from the funding multi-sig with a revoked
|
|
// commitment transaction.
|
|
BreachTransaction *wire.MsgTx
|
|
|
|
// RevokedStateNum is the revoked state number which was broadcast.
|
|
RevokedStateNum uint64
|
|
|
|
// PendingHTLCs is a slice of the HTLCs which were pending at this
|
|
// point within the channel's history transcript.
|
|
PendingHTLCs []*channeldb.HTLC
|
|
|
|
// LocalOutputSignDesc is a SignDescriptor which is capable of
|
|
// generating the signature necessary to sweep the output within the
|
|
// BreachTransaction that pays directly us.
|
|
LocalOutputSignDesc SignDescriptor
|
|
|
|
// LocalOutpoint is the outpoint of the output paying to us (the local
|
|
// party) within the breach transaction.
|
|
LocalOutpoint wire.OutPoint
|
|
|
|
// RemoteOutputSignDesc is a SignDescriptor which is capable of
|
|
// generating the signature required to claim the funds as described
|
|
// within the revocation clause of the remote party's commitment
|
|
// output.
|
|
RemoteOutputSignDesc SignDescriptor
|
|
|
|
// RemoteOutpoint is the output of the output paying to the remote
|
|
// party within the breach transaction.
|
|
RemoteOutpoint wire.OutPoint
|
|
|
|
// HtlcRetributions is a slice of HTLC retributions for each output
|
|
// active HTLC output within the breached commitment transaction.
|
|
HtlcRetributions []HtlcRetribution
|
|
}
|
|
|
|
// newBreachRetribution creates a new fully populated BreachRetribution for the
|
|
// passed channel, at a particular revoked state number, and one which targets
|
|
// the passed commitment transaction.
|
|
func newBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
|
|
broadcastCommitment *wire.MsgTx) (*BreachRetribution, error) {
|
|
|
|
commitHash := broadcastCommitment.TxHash()
|
|
|
|
// Query the on-disk revocation log for the snapshot which was recorded
|
|
// at this particular state num.
|
|
revokedSnapshot, err := chanState.FindPreviousState(stateNum)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// With the state number broadcast known, we can now derive/restore the
|
|
// proper revocation preimage necessary to sweep the remote party's
|
|
// output.
|
|
revocationPreimage, err := chanState.RevocationStore.LookUp(stateNum)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
commitmentSecret, commitmentPoint := btcec.PrivKeyFromBytes(btcec.S256(),
|
|
revocationPreimage[:])
|
|
|
|
// With the commitment point generated, we can now generate the four
|
|
// keys we'll need to reconstruct the commitment state,
|
|
localKey := TweakPubKey(chanState.LocalChanCfg.PaymentBasePoint,
|
|
commitmentPoint)
|
|
remoteKey := TweakPubKey(chanState.RemoteChanCfg.PaymentBasePoint,
|
|
commitmentPoint)
|
|
remoteDelayKey := TweakPubKey(chanState.RemoteChanCfg.DelayBasePoint,
|
|
commitmentPoint)
|
|
|
|
// Once we derive the revocation leaf, we can then re-create the
|
|
// revocation public key used within this state. This is needed in
|
|
// order to create the proper script below.
|
|
revocationKey := DeriveRevocationPubkey(
|
|
chanState.LocalChanCfg.RevocationBasePoint,
|
|
commitmentPoint,
|
|
)
|
|
|
|
// Next, reconstruct the scripts as they were present at this state
|
|
// number so we can have the proper witness script to sign and include
|
|
// within the final witness.
|
|
remoteDelay := uint32(chanState.RemoteChanCfg.CsvDelay)
|
|
remotePkScript, err := commitScriptToSelf(remoteDelay, remoteDelayKey,
|
|
revocationKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
remoteWitnessHash, err := witnessScriptHash(remotePkScript)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
localPkScript, err := commitScriptUnencumbered(localKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
localWitnessHash, err := witnessScriptHash(localPkScript)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// In order to fully populate the breach retribution struct, we'll need
|
|
// to find the exact index of the local+remote commitment outputs.
|
|
localOutpoint := wire.OutPoint{
|
|
Hash: commitHash,
|
|
}
|
|
remoteOutpoint := wire.OutPoint{
|
|
Hash: commitHash,
|
|
}
|
|
for i, txOut := range broadcastCommitment.TxOut {
|
|
switch {
|
|
case bytes.Equal(txOut.PkScript, localPkScript):
|
|
localOutpoint.Index = uint32(i)
|
|
case bytes.Equal(txOut.PkScript, remoteWitnessHash):
|
|
remoteOutpoint.Index = uint32(i)
|
|
}
|
|
}
|
|
|
|
// With the commitment outputs located, we'll now generate all the
|
|
// retribution structs for each of the HTLC transactions active on the
|
|
// remote commitment transaction.
|
|
htlcRetributions := make([]HtlcRetribution, len(chanState.Htlcs))
|
|
for i, htlc := range revokedSnapshot.Htlcs {
|
|
var (
|
|
htlcScript []byte
|
|
err error
|
|
)
|
|
|
|
// If this is an incoming HTLC, then this means that they were
|
|
// the sender of the HTLC (relative to us). So we'll
|
|
// re-generate the sender HTLC script.
|
|
if htlc.Incoming {
|
|
htlcScript, err = senderHTLCScript(localKey, remoteKey,
|
|
revocationKey, htlc.RHash[:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Otherwise, is this was an outgoing HTLC that we sent, then
|
|
// from the PoV of the remote commitment state, they're the
|
|
// receiver of this HTLC.
|
|
} else {
|
|
htlcScript, err = receiverHTLCScript(
|
|
htlc.RefundTimeout, localKey, remoteKey,
|
|
revocationKey, htlc.RHash[:],
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
htlcRetributions[i] = HtlcRetribution{
|
|
SignDesc: SignDescriptor{
|
|
PubKey: chanState.LocalChanCfg.RevocationBasePoint,
|
|
DoubleTweak: commitmentSecret,
|
|
WitnessScript: htlcScript,
|
|
Output: &wire.TxOut{
|
|
Value: int64(htlc.Amt),
|
|
},
|
|
HashType: txscript.SigHashAll,
|
|
},
|
|
OutPoint: wire.OutPoint{
|
|
Hash: commitHash,
|
|
Index: uint32(htlc.OutputIndex),
|
|
},
|
|
}
|
|
}
|
|
|
|
// We'll need to reconstruct the single tweak so we can sweep our
|
|
// non-delayed pay-to-self output self.
|
|
singleTweak := SingleTweakBytes(commitmentPoint,
|
|
chanState.LocalChanCfg.PaymentBasePoint)
|
|
|
|
// Finally, with all the necessary data constructed, we can create the
|
|
// BreachRetribution struct which houses all the data necessary to
|
|
// swiftly bring justice to the cheating remote party.
|
|
return &BreachRetribution{
|
|
BreachTransaction: broadcastCommitment,
|
|
RevokedStateNum: stateNum,
|
|
PendingHTLCs: revokedSnapshot.Htlcs,
|
|
LocalOutpoint: localOutpoint,
|
|
LocalOutputSignDesc: SignDescriptor{
|
|
SingleTweak: singleTweak,
|
|
PubKey: chanState.LocalChanCfg.PaymentBasePoint,
|
|
WitnessScript: localPkScript,
|
|
Output: &wire.TxOut{
|
|
PkScript: localWitnessHash,
|
|
Value: int64(revokedSnapshot.LocalBalance),
|
|
},
|
|
HashType: txscript.SigHashAll,
|
|
},
|
|
RemoteOutpoint: remoteOutpoint,
|
|
RemoteOutputSignDesc: SignDescriptor{
|
|
PubKey: chanState.LocalChanCfg.RevocationBasePoint,
|
|
DoubleTweak: commitmentSecret,
|
|
WitnessScript: remotePkScript,
|
|
Output: &wire.TxOut{
|
|
PkScript: remoteWitnessHash,
|
|
Value: int64(revokedSnapshot.RemoteBalance),
|
|
},
|
|
HashType: txscript.SigHashAll,
|
|
},
|
|
HtlcRetributions: htlcRetributions,
|
|
}, nil
|
|
}
|
|
|
|
// closeObserver is a goroutine which watches the network for any spends of the
|
|
// multi-sig funding output. A spend from the multi-sig output may occur under
|
|
// the following three scenarios: a cooperative close, a unilateral close, and
|
|
// a uncooperative contract breaching close. In the case of the last scenario a
|
|
// BreachRetribution struct is created and sent over the ContractBreach channel
|
|
// notifying subscribers that the counterparty has violated the condition of
|
|
// the channel by broadcasting a revoked prior state.
|
|
//
|
|
// NOTE: This MUST be run as a goroutine.
|
|
func (lc *LightningChannel) closeObserver(channelCloseNtfn *chainntnfs.SpendEvent) {
|
|
defer lc.wg.Done()
|
|
|
|
walletLog.Infof("Close observer for ChannelPoint(%v) active",
|
|
lc.channelState.FundingOutpoint)
|
|
|
|
var (
|
|
commitSpend *chainntnfs.SpendDetail
|
|
ok bool
|
|
)
|
|
|
|
select {
|
|
// If the daemon is shutting down, then this notification channel will
|
|
// be closed, so check the second read-value to avoid a false positive.
|
|
case commitSpend, ok = <-channelCloseNtfn.Spend:
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
// Otherwise, we've beeen signalled to bail out early by the
|
|
// caller/maintainer of this channel.
|
|
case <-lc.quit:
|
|
// As we're exiting before the spend notification has been
|
|
// triggered, we'll cancel the notification intent so the
|
|
// ChainNotiifer can free up the resources.
|
|
channelCloseNtfn.Cancel()
|
|
return
|
|
}
|
|
|
|
// If we've already initiated a local cooperative or unilateral close
|
|
// locally, then we have nothing more to do.
|
|
lc.RLock()
|
|
if lc.status == channelClosed || lc.status == channelDispute ||
|
|
lc.status == channelClosing {
|
|
|
|
lc.RUnlock()
|
|
return
|
|
}
|
|
lc.RUnlock()
|
|
|
|
// Otherwise, the remote party might have broadcast a prior revoked
|
|
// state...!!!
|
|
commitTxBroadcast := commitSpend.SpendingTx
|
|
|
|
// If this is our commitment transaction, then we can exit here as we
|
|
// don't have any further processing we need to do (we can't cheat
|
|
// ourselves :p).
|
|
commitmentHash := lc.channelState.CommitTx.TxHash()
|
|
isOurCommitment := commitSpend.SpenderTxHash.IsEqual(&commitmentHash)
|
|
if isOurCommitment {
|
|
return
|
|
}
|
|
|
|
lc.Lock()
|
|
defer lc.Unlock()
|
|
|
|
walletLog.Warnf("Unprompted commitment broadcast for ChannelPoint(%v) "+
|
|
"detected!", lc.channelState.FundingOutpoint)
|
|
|
|
// Decode the state hint encoded within the commitment transaction to
|
|
// determine if this is a revoked state or not.
|
|
obsfucator := lc.stateHintObsfucator
|
|
broadcastStateNum := GetStateNumHint(commitTxBroadcast, obsfucator)
|
|
|
|
currentStateNum := lc.currentHeight
|
|
|
|
// TODO(roasbeef): track heights distinctly?
|
|
|
|
switch {
|
|
// If state number spending transaction matches the current latest
|
|
// state, then they've initiated a unilateral close. So we'll trigger
|
|
// the unilateral close signal so subscribers can clean up the state as
|
|
// necessary.
|
|
//
|
|
// We'll also handle the case of the remote party broadcasting their
|
|
// commitment transaction which is one height above ours. This case an
|
|
// arise when we initiate a state transition, but the remote party has
|
|
// a fail crash _after_ accepting the new state, but _before_ sending
|
|
// their signature to us.
|
|
case broadcastStateNum >= currentStateNum:
|
|
walletLog.Infof("Unilateral close of ChannelPoint(%v) "+
|
|
"detected", lc.channelState.FundingOutpoint)
|
|
|
|
// As we've detected that the channel has been closed,
|
|
// immediately delete the state from disk, creating a close
|
|
// summary for future usage by related sub-systems.
|
|
//
|
|
// TODO(roasbeef): include HTLC's
|
|
// * and time-locked balance, NEED TO???
|
|
closeSummary := channeldb.ChannelCloseSummary{
|
|
ChanPoint: lc.channelState.FundingOutpoint,
|
|
ClosingTXID: *commitSpend.SpenderTxHash,
|
|
RemotePub: lc.channelState.IdentityPub,
|
|
Capacity: lc.Capacity,
|
|
SettledBalance: lc.channelState.LocalBalance,
|
|
CloseType: channeldb.ForceClose,
|
|
IsPending: true,
|
|
}
|
|
if err := lc.DeleteState(&closeSummary); err != nil {
|
|
walletLog.Errorf("unable to delete channel state: %v",
|
|
err)
|
|
}
|
|
|
|
// First, we'll generate the commitment point and the
|
|
// revocation point so we can re-construct the HTLC state and
|
|
// also our payment key.
|
|
commitPoint := lc.channelState.RemoteCurrentRevocation
|
|
revokeKey := DeriveRevocationPubkey(
|
|
lc.localChanCfg.RevocationBasePoint,
|
|
commitPoint,
|
|
)
|
|
|
|
// Next, we'll obtain HTLC resolutions for all the outgoing
|
|
// HTLC's we had on their commitment transaction.
|
|
htlcResolutions, localKey, err := extractHtlcResolutions(
|
|
lc.channelState.FeePerKw, false, lc.signer,
|
|
lc.channelState.Htlcs, commitPoint,
|
|
revokeKey, lc.localChanCfg, lc.remoteChanCfg,
|
|
*commitSpend.SpenderTxHash)
|
|
if err != nil {
|
|
walletLog.Errorf("unable to create htlc "+
|
|
"resolutions: %v", err)
|
|
return
|
|
}
|
|
|
|
// Before we can generate the proper sign descriptor, we'll
|
|
// need to locate the output index of our non-delayed output on
|
|
// the commitment transaction.
|
|
selfP2WKH, err := commitScriptUnencumbered(localKey)
|
|
if err != nil {
|
|
walletLog.Errorf("unable to create self commit "+
|
|
"script: %v", err)
|
|
return
|
|
}
|
|
var selfPoint *wire.OutPoint
|
|
for outputIndex, txOut := range commitTxBroadcast.TxOut {
|
|
if bytes.Equal(txOut.PkScript, selfP2WKH) {
|
|
selfPoint = &wire.OutPoint{
|
|
Hash: *commitSpend.SpenderTxHash,
|
|
Index: uint32(outputIndex),
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
// With the HTLC's taken care of, we'll generate the sign
|
|
// descriptor necessary to sweep our commitment output, but
|
|
// only if we had a non-trimmed balance.
|
|
var selfSignDesc *SignDescriptor
|
|
if selfPoint != nil {
|
|
localPayBase := lc.localChanCfg.PaymentBasePoint
|
|
selfSignDesc = &SignDescriptor{
|
|
PubKey: localPayBase,
|
|
SingleTweak: SingleTweakBytes(commitPoint, localPayBase),
|
|
WitnessScript: selfP2WKH,
|
|
Output: &wire.TxOut{
|
|
Value: int64(lc.channelState.LocalBalance),
|
|
PkScript: selfP2WKH,
|
|
},
|
|
HashType: txscript.SigHashAll,
|
|
}
|
|
}
|
|
|
|
// TODO(roasbeef): send msg before writing to disk
|
|
// * need to ensure proper fault tolerance in all cases
|
|
// * get ACK from the consumer of the ntfn before writing to disk?
|
|
// * no harm in repeated ntfns: at least once semantics
|
|
|
|
// Notify any subscribers that we've detected a unilateral
|
|
// commitment transaction broadcast.
|
|
close(lc.UnilateralCloseSignal)
|
|
|
|
// We'll also send all the details necessary to re-claim funds
|
|
// that are suspended within any contracts.
|
|
lc.UnilateralClose <- &UnilateralCloseSummary{
|
|
SpendDetail: commitSpend,
|
|
ChannelCloseSummary: closeSummary,
|
|
SelfOutPoint: selfPoint,
|
|
SelfOutputSignDesc: selfSignDesc,
|
|
HtlcResolutions: htlcResolutions,
|
|
}
|
|
|
|
// If the state number broadcast is lower than the remote node's
|
|
// current un-revoked height, then THEY'RE ATTEMPTING TO VIOLATE THE
|
|
// CONTRACT LAID OUT WITHIN THE PAYMENT CHANNEL. Therefore we close
|
|
// the signal indicating a revoked broadcast to allow subscribers to
|
|
// swiftly dispatch justice!!!
|
|
case broadcastStateNum < currentStateNum:
|
|
walletLog.Warnf("Remote peer has breached the channel "+
|
|
"contract for ChannelPoint(%v). Revoked state #%v was "+
|
|
"broadcast!!!", lc.channelState.FundingOutpoint,
|
|
broadcastStateNum)
|
|
|
|
// Create a new reach retribution struct which contains all the
|
|
// data needed to swiftly bring the cheating peer to justice.
|
|
retribution, err := newBreachRetribution(lc.channelState,
|
|
broadcastStateNum, commitTxBroadcast)
|
|
if err != nil {
|
|
walletLog.Errorf("unable to create breach retribution: %v", err)
|
|
return
|
|
}
|
|
|
|
walletLog.Debugf("Punishment breach retribution created: %v",
|
|
spew.Sdump(retribution))
|
|
|
|
// Finally, send the retribution struct over the contract beach
|
|
// channel to allow the observer the use the breach retribution
|
|
// to sweep ALL funds.
|
|
lc.ContractBreach <- retribution
|
|
}
|
|
}
|
|
|
|
// htlcTimeoutFee returns the fee in satoshis required for an HTLC timeout
|
|
// transaction based on the current fee rate.
|
|
func htlcTimeoutFee(feePerKw btcutil.Amount) btcutil.Amount {
|
|
return (feePerKw * HtlcTimeoutWeight) / 1000
|
|
}
|
|
|
|
// htlcSuccessFee returns the fee in satoshis required for an HTLC success
|
|
// transaction based on the current fee rate.
|
|
func htlcSuccessFee(feePerKw btcutil.Amount) btcutil.Amount {
|
|
return (feePerKw * HtlcSuccessWeight) / 1000
|
|
}
|
|
|
|
// htlcIsDust determines if an HTLC output is dust or not depending on two
|
|
// bits: if the HTLC is incoming and if the HTLC will be placed on our
|
|
// commitment transaction, or theirs. These two pieces of information are
|
|
// require as we currently used second-level HTLC transactions ass off-chain
|
|
// covenants. Depending on the two bits, we'll either be using a timeout or
|
|
// success transaction which have different weights.
|
|
func htlcIsDust(incoming, ourCommit bool,
|
|
feePerKw, htlcAmt, dustLimit btcutil.Amount) bool {
|
|
|
|
// First we'll determine the fee required for this HTLC based on if this is
|
|
// an incoming HTLC or not, and also on whose commitment transaction it
|
|
// will be placed on.
|
|
var htlcFee btcutil.Amount
|
|
switch {
|
|
|
|
// If this is an incoming HTLC on our commitment transaction, then the
|
|
// second-level transaction will be a success transaction.
|
|
case incoming && ourCommit:
|
|
htlcFee = htlcSuccessFee(feePerKw)
|
|
|
|
// If this is an incoming HTLC on their commitment transaction, then
|
|
// we'll be using a second-level timeout transaction as they've added
|
|
// this HTLC.
|
|
case incoming && !ourCommit:
|
|
htlcFee = htlcTimeoutFee(feePerKw)
|
|
|
|
// If this is an outgoing HTLC on our commitment transaction, then
|
|
// we'll be using a timeout transaction as we're the sender of the
|
|
// HTLC.
|
|
case !incoming && ourCommit:
|
|
htlcFee = htlcTimeoutFee(feePerKw)
|
|
|
|
// If this is an outgoing HTLC on their commitment transaction, then
|
|
// we'll be using an HTLC success transaction as they're the receiver
|
|
// of this HTLC.
|
|
case !incoming && !ourCommit:
|
|
htlcFee = htlcTimeoutFee(feePerKw)
|
|
}
|
|
|
|
return (htlcAmt - htlcFee) < dustLimit
|
|
}
|
|
|
|
// restoreStateLogs runs through the current locked-in HTLCs from the point of
|
|
// view of the channel and insert corresponding log entries (both local and
|
|
// remote) for each HTLC read from disk. This method is required to sync the
|
|
// in-memory state of the state machine with that read from persistent storage.
|
|
func (lc *LightningChannel) restoreStateLogs() error {
|
|
var pastHeight uint64
|
|
if lc.currentHeight > 0 {
|
|
pastHeight = lc.currentHeight - 1
|
|
}
|
|
|
|
// Obtain the local and remote channel configurations. These house all
|
|
// the relevant public keys and points we'll need in order to restore
|
|
// the state log.
|
|
localChanCfg := lc.localChanCfg
|
|
remoteChanCfg := lc.remoteChanCfg
|
|
|
|
// In order to reconstruct the pkScripts on each of the pending HTLC
|
|
// outputs (if any) we'll need to regenerate the current revocation for
|
|
// this current un-revoked state.
|
|
ourRevPreImage, err := lc.channelState.RevocationProducer.AtIndex(lc.currentHeight)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// With the commitment secret recovered, we'll generate the revocation
|
|
// used on the *local* commitment transaction. This is computed using
|
|
// the point derived from the commitment secret at the remote party's
|
|
// revocation based.
|
|
localCommitPoint := ComputeCommitmentPoint(ourRevPreImage[:])
|
|
localRevocation := DeriveRevocationPubkey(
|
|
remoteChanCfg.RevocationBasePoint,
|
|
localCommitPoint,
|
|
)
|
|
remoteCommitPoint := lc.channelState.RemoteCurrentRevocation
|
|
remoteRevocation := DeriveRevocationPubkey(
|
|
localChanCfg.RevocationBasePoint,
|
|
remoteCommitPoint,
|
|
)
|
|
|
|
// Additionally, we'll fetch the current payment base points which are
|
|
// required to fully generate the scripts.
|
|
localCommitLocalKey := TweakPubKey(localChanCfg.PaymentBasePoint,
|
|
localCommitPoint)
|
|
localCommitRemoteKey := TweakPubKey(remoteChanCfg.PaymentBasePoint,
|
|
localCommitPoint)
|
|
|
|
remoteCommitLocalKey := TweakPubKey(localChanCfg.PaymentBasePoint,
|
|
remoteCommitPoint)
|
|
remoteCommitRemoteKey := TweakPubKey(remoteChanCfg.PaymentBasePoint,
|
|
remoteCommitPoint)
|
|
|
|
var ourCounter, theirCounter uint64
|
|
|
|
// Grab the current fee rate as we'll need this to determine if the
|
|
// prior HTLC's were considered dust or not at this particular
|
|
// commitment sate.
|
|
feeRate := lc.channelState.FeePerKw
|
|
|
|
// TODO(roasbeef): partition entries added based on our current review
|
|
// an our view of them from the log?
|
|
for _, htlc := range lc.channelState.Htlcs {
|
|
// TODO(roasbeef): set isForwarded to false for all? need to
|
|
// persist state w.r.t to if forwarded or not, or can
|
|
// inadvertently trigger replays
|
|
|
|
// The proper pkScripts for this PaymentDescriptor must be
|
|
// generated so we can easily locate them within the commitment
|
|
// transaction in the future.
|
|
var ourP2WSH, theirP2WSH, ourWitnessScript, theirWitnessScript []byte
|
|
|
|
// If the either outputs is dust from the local or remote
|
|
// node's perspective, then we don't need to generate the
|
|
// scripts as we only generate them in order to locate the
|
|
// outputs within the commitment transaction. As we'll mark
|
|
// dust with a special output index in the on-disk state
|
|
// snapshot.
|
|
isDustLocal := htlcIsDust(htlc.Incoming, true, feeRate,
|
|
htlc.Amt, localChanCfg.DustLimit)
|
|
isDustRemote := htlcIsDust(htlc.Incoming, false, feeRate,
|
|
htlc.Amt, remoteChanCfg.DustLimit)
|
|
if !isDustLocal {
|
|
ourP2WSH, ourWitnessScript, err = lc.genHtlcScript(
|
|
htlc.Incoming, true, htlc.RefundTimeout, htlc.RHash,
|
|
localCommitLocalKey, localCommitRemoteKey,
|
|
localRevocation)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if !isDustRemote {
|
|
theirP2WSH, theirWitnessScript, err = lc.genHtlcScript(
|
|
htlc.Incoming, false, htlc.RefundTimeout, htlc.RHash,
|
|
remoteCommitLocalKey, remoteCommitRemoteKey,
|
|
remoteRevocation)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
pd := &PaymentDescriptor{
|
|
RHash: htlc.RHash,
|
|
Timeout: htlc.RefundTimeout,
|
|
Amount: htlc.Amt,
|
|
EntryType: Add,
|
|
addCommitHeightRemote: pastHeight,
|
|
addCommitHeightLocal: pastHeight,
|
|
ourPkScript: ourP2WSH,
|
|
ourWitnessScript: ourWitnessScript,
|
|
theirPkScript: theirP2WSH,
|
|
theirWitnessScript: theirWitnessScript,
|
|
}
|
|
|
|
if !htlc.Incoming {
|
|
pd.Index = ourCounter
|
|
lc.localUpdateLog.appendUpdate(pd)
|
|
|
|
ourCounter++
|
|
} else {
|
|
pd.Index = theirCounter
|
|
lc.remoteUpdateLog.appendUpdate(pd)
|
|
lc.rHashMap[pd.RHash] = append(lc.rHashMap[pd.RHash], pd)
|
|
|
|
theirCounter++
|
|
}
|
|
}
|
|
|
|
lc.localCommitChain.tail().ourMessageIndex = ourCounter
|
|
lc.localCommitChain.tail().theirMessageIndex = theirCounter
|
|
lc.remoteCommitChain.tail().ourMessageIndex = ourCounter
|
|
lc.remoteCommitChain.tail().theirMessageIndex = theirCounter
|
|
|
|
return nil
|
|
}
|
|
|
|
// htlcView represents the "active" HTLCs at a particular point within the
|
|
// history of the HTLC update log.
|
|
type htlcView struct {
|
|
ourUpdates []*PaymentDescriptor
|
|
theirUpdates []*PaymentDescriptor
|
|
}
|
|
|
|
// fetchHTLCView returns all the candidate HTLC updates which should be
|
|
// considered for inclusion within a commitment based on the passed HTLC log
|
|
// indexes.
|
|
func (lc *LightningChannel) fetchHTLCView(theirLogIndex, ourLogIndex uint64) *htlcView {
|
|
var ourHTLCs []*PaymentDescriptor
|
|
for e := lc.localUpdateLog.Front(); e != nil; e = e.Next() {
|
|
htlc := e.Value.(*PaymentDescriptor)
|
|
|
|
// This HTLC is active from this point-of-view iff the log
|
|
// index of the state update is below the specified index in
|
|
// our update log.
|
|
if htlc.Index < ourLogIndex {
|
|
ourHTLCs = append(ourHTLCs, htlc)
|
|
}
|
|
}
|
|
|
|
var theirHTLCs []*PaymentDescriptor
|
|
for e := lc.remoteUpdateLog.Front(); e != nil; e = e.Next() {
|
|
htlc := e.Value.(*PaymentDescriptor)
|
|
|
|
// If this is an incoming HTLC, then it is only active from
|
|
// this point-of-view if the index of the HTLC addition in
|
|
// their log is below the specified view index.
|
|
if htlc.Index < theirLogIndex {
|
|
theirHTLCs = append(theirHTLCs, htlc)
|
|
}
|
|
}
|
|
|
|
return &htlcView{
|
|
ourUpdates: ourHTLCs,
|
|
theirUpdates: theirHTLCs,
|
|
}
|
|
}
|
|
|
|
// fetchCommitmentView returns a populated commitment which expresses the state
|
|
// of the channel from the point of view of a local or remote chain, evaluating
|
|
// the HTLC log up to the passed indexes. This function is used to construct
|
|
// both local and remote commitment transactions in order to sign or verify new
|
|
// commitment updates. A fully populated commitment is returned which reflects
|
|
// the proper balances for both sides at this point in the commitment chain.
|
|
//
|
|
// TODO(roasbeef): update commit to to have all keys?
|
|
func (lc *LightningChannel) fetchCommitmentView(remoteChain bool,
|
|
ourLogIndex, theirLogIndex uint64,
|
|
commitPoint *btcec.PublicKey) (*commitment, error) {
|
|
|
|
commitChain := lc.localCommitChain
|
|
if remoteChain {
|
|
commitChain = lc.remoteCommitChain
|
|
}
|
|
|
|
ourCommitTx := !remoteChain
|
|
ourBalance := commitChain.tip().ourBalance
|
|
theirBalance := commitChain.tip().theirBalance
|
|
|
|
// Add the fee from the previous commitment state back to the
|
|
// initiator's balance, so that the fee can be recalculated and
|
|
// re-applied in case fee estimation parameters have changed or the
|
|
// number of outstanding HTLCs has changed.
|
|
if lc.channelState.IsInitiator {
|
|
ourBalance = ourBalance + commitChain.tip().fee
|
|
} else if !lc.channelState.IsInitiator {
|
|
theirBalance = theirBalance + commitChain.tip().fee
|
|
}
|
|
|
|
nextHeight := commitChain.tip().height + 1
|
|
|
|
// Run through all the HTLCs that will be covered by this transaction
|
|
// in order to update their commitment addition height, and to adjust
|
|
// the balances on the commitment transaction accordingly.
|
|
// TODO(roasbeef): error if log empty?
|
|
htlcView := lc.fetchHTLCView(theirLogIndex, ourLogIndex)
|
|
filteredHTLCView := lc.evaluateHTLCView(htlcView, &ourBalance,
|
|
&theirBalance, nextHeight, remoteChain)
|
|
|
|
// Initiate feePerKw to the last committed fee for this chain as we'll
|
|
// need this to determine which HTLC's are dust, and also the final fee
|
|
// rate.
|
|
feePerKw := commitChain.tail().feePerKw
|
|
|
|
// Check if any fee updates have taken place since that last
|
|
// commitment.
|
|
if lc.channelState.IsInitiator {
|
|
switch {
|
|
// We've sent an update_fee message since our last commitment,
|
|
// and now are now creating a commitment that reflects the new
|
|
// fee update.
|
|
case remoteChain && lc.pendingFeeUpdate != nil:
|
|
feePerKw = *lc.pendingFeeUpdate
|
|
|
|
// We've created a new commitment for the remote chain that
|
|
// includes a fee update, and have not received a commitment
|
|
// after the fee update has been ACked.
|
|
case !remoteChain && lc.pendingAckFeeUpdate != nil:
|
|
feePerKw = *lc.pendingAckFeeUpdate
|
|
}
|
|
} else {
|
|
switch {
|
|
// We've received a fee update since the last local commitment,
|
|
// so we'll include the fee update in the current view.
|
|
case !remoteChain && lc.pendingFeeUpdate != nil:
|
|
feePerKw = *lc.pendingFeeUpdate
|
|
|
|
// Earlier we received a commitment that signed an earlier fee
|
|
// update, and now we must ACK that update.
|
|
case remoteChain && lc.pendingAckFeeUpdate != nil:
|
|
feePerKw = *lc.pendingAckFeeUpdate
|
|
}
|
|
}
|
|
|
|
// Determine how many current HTLCs are over the dust limit, and should
|
|
// be counted for the purpose of fee calculation.
|
|
var dustLimit, dustFees btcutil.Amount
|
|
if remoteChain {
|
|
dustLimit = lc.remoteChanCfg.DustLimit
|
|
} else {
|
|
dustLimit = lc.localChanCfg.DustLimit
|
|
}
|
|
numHTLCs := 0
|
|
for _, htlc := range filteredHTLCView.ourUpdates {
|
|
if htlcIsDust(false, ourCommitTx, feePerKw, htlc.Amount,
|
|
dustLimit) {
|
|
|
|
dustFees += htlc.Amount
|
|
continue
|
|
}
|
|
|
|
numHTLCs++
|
|
}
|
|
for _, htlc := range filteredHTLCView.theirUpdates {
|
|
if htlcIsDust(true, ourCommitTx, feePerKw, htlc.Amount,
|
|
dustLimit) {
|
|
|
|
dustFees += htlc.Amount
|
|
continue
|
|
}
|
|
|
|
numHTLCs++
|
|
}
|
|
|
|
// Next, we'll calculate the fee for the commitment transaction based
|
|
// on its total weight. Once we have the total weight, we'll multiply
|
|
// by the current fee-per-kw, then divide by 1000 to get the proper
|
|
// fee.
|
|
totalCommitWeight := commitWeight + btcutil.Amount(htlcWeight*numHTLCs)
|
|
|
|
// With the weight known, we can now calculate the commitment fee,
|
|
// ensuring that we account for any dust outputs trimmed above.
|
|
commitFee := (feePerKw * totalCommitWeight) / 1000
|
|
commitFee -= dustFees
|
|
|
|
// Currently, within the protocol, the initiator always pays the fees.
|
|
// So we'll subtract the fee amount from the balance of the current
|
|
// initiator.
|
|
if lc.channelState.IsInitiator {
|
|
ourBalance = ourBalance - commitFee
|
|
} else if !lc.channelState.IsInitiator {
|
|
theirBalance = theirBalance - commitFee
|
|
}
|
|
|
|
var (
|
|
delayKey, paymentKey, revocationKey *btcec.PublicKey
|
|
delay uint32
|
|
delayBalance, p2wkhBalance btcutil.Amount
|
|
)
|
|
|
|
// We'll now compute the delay, payment and revocation key based on the
|
|
// current commitment point. All keys are tweaked each state in order
|
|
// to ensure the keys from each state are unlinkable. TO create the
|
|
// revocation key, we take the opposite party's revocation base point
|
|
// and combine that with the current commitment point.
|
|
if remoteChain {
|
|
delayKey = TweakPubKey(lc.remoteChanCfg.DelayBasePoint,
|
|
commitPoint)
|
|
paymentKey = TweakPubKey(lc.localChanCfg.PaymentBasePoint,
|
|
commitPoint)
|
|
revocationKey = DeriveRevocationPubkey(
|
|
lc.localChanCfg.RevocationBasePoint,
|
|
commitPoint,
|
|
)
|
|
|
|
delay = uint32(lc.remoteChanCfg.CsvDelay)
|
|
delayBalance = theirBalance
|
|
p2wkhBalance = ourBalance
|
|
} else {
|
|
delayKey = TweakPubKey(lc.localChanCfg.DelayBasePoint,
|
|
commitPoint)
|
|
paymentKey = TweakPubKey(lc.remoteChanCfg.PaymentBasePoint,
|
|
commitPoint)
|
|
revocationKey = DeriveRevocationPubkey(
|
|
lc.remoteChanCfg.RevocationBasePoint,
|
|
commitPoint,
|
|
)
|
|
|
|
delay = uint32(lc.localChanCfg.CsvDelay)
|
|
delayBalance = ourBalance
|
|
p2wkhBalance = theirBalance
|
|
}
|
|
|
|
// TODO(roasbeef); create all keys unconditionally within commitment
|
|
// store in commitment, will need all when doing HTLC's
|
|
|
|
// Generate a new commitment transaction with all the latest
|
|
// unsettled/un-timed out HTLCs.
|
|
commitTx, err := CreateCommitTx(lc.fundingTxIn, delayKey, paymentKey,
|
|
revocationKey, delay, delayBalance, p2wkhBalance, dustLimit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// We'll now add all the HTLC outputs to the commitment transaction.
|
|
// Each output includes an off-chain 2-of-2 covenant clause, so we'll
|
|
// need the objective local/remote keys for this particular commitment
|
|
// as well.
|
|
// TODO(roasbeef): could avoid computing them both here
|
|
localKey := TweakPubKey(lc.localChanCfg.PaymentBasePoint, commitPoint)
|
|
remoteKey := TweakPubKey(lc.remoteChanCfg.PaymentBasePoint, commitPoint)
|
|
for _, htlc := range filteredHTLCView.ourUpdates {
|
|
if htlcIsDust(false, !remoteChain, feePerKw, htlc.Amount,
|
|
dustLimit) {
|
|
continue
|
|
}
|
|
|
|
err := lc.addHTLC(commitTx, ourCommitTx, false, htlc, localKey,
|
|
remoteKey, revocationKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
for _, htlc := range filteredHTLCView.theirUpdates {
|
|
if htlcIsDust(true, !remoteChain, feePerKw, htlc.Amount,
|
|
dustLimit) {
|
|
continue
|
|
}
|
|
|
|
err := lc.addHTLC(commitTx, ourCommitTx, true, htlc, localKey,
|
|
remoteKey, revocationKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Set the state hint of the commitment transaction to facilitate
|
|
// quickly recovering the necessary penalty state in the case of an
|
|
// uncooperative broadcast.
|
|
obsfucator := lc.stateHintObsfucator
|
|
stateNum := nextHeight
|
|
if err := SetStateNumHint(commitTx, stateNum, obsfucator); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Sort the transactions according to the agreed upon canonical
|
|
// ordering. This lets us skip sending the entire transaction over,
|
|
// instead we'll just send signatures.
|
|
txsort.InPlaceSort(commitTx)
|
|
c := &commitment{
|
|
txn: commitTx,
|
|
height: nextHeight,
|
|
ourBalance: ourBalance,
|
|
ourMessageIndex: ourLogIndex,
|
|
theirMessageIndex: theirLogIndex,
|
|
theirBalance: theirBalance,
|
|
fee: commitFee,
|
|
feePerKw: feePerKw,
|
|
}
|
|
|
|
// In order to ensure _none_ of the HTLC's associated with this new
|
|
// commitment are mutated, we'll manually copy over each HTLC to its
|
|
// respective slice.
|
|
c.outgoingHTLCs = make([]PaymentDescriptor, len(filteredHTLCView.ourUpdates))
|
|
for i, htlc := range filteredHTLCView.ourUpdates {
|
|
c.outgoingHTLCs[i] = *htlc
|
|
}
|
|
c.incomingHTLCs = make([]PaymentDescriptor, len(filteredHTLCView.theirUpdates))
|
|
for i, htlc := range filteredHTLCView.theirUpdates {
|
|
c.incomingHTLCs[i] = *htlc
|
|
}
|
|
|
|
// Finally, we'll populate all the HTLC indexes so we can track the
|
|
// locations of each HTLC in the commitment state.
|
|
if err := c.populateHtlcIndexes(ourCommitTx, dustLimit); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return c, nil
|
|
}
|
|
|
|
// evaluateHTLCView processes all update entries in both HTLC update logs,
|
|
// producing a final view which is the result of properly applying all adds,
|
|
// settles, and timeouts found in both logs. The resulting view returned
|
|
// reflects the current state of HTLCs within the remote or local commitment
|
|
// chain.
|
|
func (lc *LightningChannel) evaluateHTLCView(view *htlcView, ourBalance,
|
|
theirBalance *btcutil.Amount, nextHeight uint64, remoteChain bool) *htlcView {
|
|
|
|
newView := &htlcView{}
|
|
|
|
// We use two maps, one for the local log and one for the remote log to
|
|
// keep track of which entries we need to skip when creating the final
|
|
// htlc view. We skip an entry whenever we find a settle or a timeout
|
|
// modifying an entry.
|
|
skipUs := make(map[uint64]struct{})
|
|
skipThem := make(map[uint64]struct{})
|
|
|
|
// First we run through non-add entries in both logs, populating the
|
|
// skip sets and mutating the current chain state (crediting balances,
|
|
// etc) to reflect the settle/timeout entry encountered.
|
|
for _, entry := range view.ourUpdates {
|
|
if entry.EntryType == Add {
|
|
continue
|
|
}
|
|
|
|
// If we're settling in inbound HTLC, and it hasn't been
|
|
// processed, yet, the increment our state tracking the total
|
|
// number of satoshis we've received within the channel.
|
|
if entry.EntryType == Settle && !remoteChain &&
|
|
entry.removeCommitHeightLocal == 0 {
|
|
lc.channelState.TotalSatoshisReceived += uint64(entry.Amount)
|
|
}
|
|
|
|
addEntry := lc.remoteUpdateLog.lookup(entry.ParentIndex)
|
|
|
|
skipThem[addEntry.Index] = struct{}{}
|
|
processRemoveEntry(entry, ourBalance, theirBalance,
|
|
nextHeight, remoteChain, true)
|
|
}
|
|
for _, entry := range view.theirUpdates {
|
|
if entry.EntryType == Add {
|
|
continue
|
|
}
|
|
|
|
// If the remote party is settling one of our outbound HTLC's,
|
|
// and it hasn't been processed, yet, the increment our state
|
|
// tracking the total number of satoshis we've sent within the
|
|
// channel.
|
|
if entry.EntryType == Settle && !remoteChain &&
|
|
entry.removeCommitHeightLocal == 0 {
|
|
lc.channelState.TotalSatoshisSent += uint64(entry.Amount)
|
|
}
|
|
|
|
addEntry := lc.localUpdateLog.lookup(entry.ParentIndex)
|
|
|
|
skipUs[addEntry.Index] = struct{}{}
|
|
processRemoveEntry(entry, ourBalance, theirBalance,
|
|
nextHeight, remoteChain, false)
|
|
}
|
|
|
|
// Next we take a second pass through all the log entries, skipping any
|
|
// settled HTLCs, and debiting the chain state balance due to any newly
|
|
// added HTLCs.
|
|
for _, entry := range view.ourUpdates {
|
|
isAdd := entry.EntryType == Add
|
|
if _, ok := skipUs[entry.Index]; !isAdd || ok {
|
|
continue
|
|
}
|
|
|
|
processAddEntry(entry, ourBalance, theirBalance, nextHeight,
|
|
remoteChain, false)
|
|
newView.ourUpdates = append(newView.ourUpdates, entry)
|
|
}
|
|
for _, entry := range view.theirUpdates {
|
|
isAdd := entry.EntryType == Add
|
|
if _, ok := skipThem[entry.Index]; !isAdd || ok {
|
|
continue
|
|
}
|
|
|
|
processAddEntry(entry, ourBalance, theirBalance, nextHeight,
|
|
remoteChain, true)
|
|
newView.theirUpdates = append(newView.theirUpdates, entry)
|
|
}
|
|
|
|
return newView
|
|
}
|
|
|
|
// processAddEntry evaluates the effect of an add entry within the HTLC log.
|
|
// If the HTLC hasn't yet been committed in either chain, then the height it
|
|
// was committed is updated. Keeping track of this inclusion height allows us to
|
|
// later compact the log once the change is fully committed in both chains.
|
|
func processAddEntry(htlc *PaymentDescriptor, ourBalance, theirBalance *btcutil.Amount,
|
|
nextHeight uint64, remoteChain bool, isIncoming bool) {
|
|
|
|
// If we're evaluating this entry for the remote chain (to create/view
|
|
// a new commitment), then we'll may be updating the height this entry
|
|
// was added to the chain. Otherwise, we may be updating the entry's
|
|
// height w.r.t the local chain.
|
|
var addHeight *uint64
|
|
if remoteChain {
|
|
addHeight = &htlc.addCommitHeightRemote
|
|
} else {
|
|
addHeight = &htlc.addCommitHeightLocal
|
|
}
|
|
|
|
if *addHeight != 0 {
|
|
return
|
|
}
|
|
|
|
if isIncoming {
|
|
// If this is a new incoming (un-committed) HTLC, then we need
|
|
// to update their balance accordingly by subtracting the
|
|
// amount of the HTLC that are funds pending.
|
|
*theirBalance -= htlc.Amount
|
|
} else {
|
|
// Similarly, we need to debit our balance if this is an out
|
|
// going HTLC to reflect the pending balance.
|
|
*ourBalance -= htlc.Amount
|
|
}
|
|
|
|
*addHeight = nextHeight
|
|
}
|
|
|
|
// processRemoveEntry processes a log entry which settles or timesout a
|
|
// previously added HTLC. If the removal entry has already been processed, it
|
|
// is skipped.
|
|
func processRemoveEntry(htlc *PaymentDescriptor, ourBalance,
|
|
theirBalance *btcutil.Amount, nextHeight uint64,
|
|
remoteChain bool, isIncoming bool) {
|
|
|
|
var removeHeight *uint64
|
|
if remoteChain {
|
|
removeHeight = &htlc.removeCommitHeightRemote
|
|
} else {
|
|
removeHeight = &htlc.removeCommitHeightLocal
|
|
}
|
|
|
|
// Ignore any removal entries which have already been processed.
|
|
if *removeHeight != 0 {
|
|
return
|
|
}
|
|
|
|
switch {
|
|
// If an incoming HTLC is being settled, then this means that we've
|
|
// received the preimage either from another subsystem, or the
|
|
// upstream peer in the route. Therefore, we increase our balance by
|
|
// the HTLC amount.
|
|
case isIncoming && htlc.EntryType == Settle:
|
|
*ourBalance += htlc.Amount
|
|
|
|
// Otherwise, this HTLC is being failed out, therefore the value of the
|
|
// HTLC should return to the remote party.
|
|
case isIncoming && htlc.EntryType == Fail:
|
|
*theirBalance += htlc.Amount
|
|
|
|
// If an outgoing HTLC is being settled, then this means that the
|
|
// downstream party resented the preimage or learned of it via a
|
|
// downstream peer. In either case, we credit their settled value with
|
|
// the value of the HTLC.
|
|
case !isIncoming && htlc.EntryType == Settle:
|
|
*theirBalance += htlc.Amount
|
|
|
|
// Otherwise, one of our outgoing HTLC's has timed out, so the value of
|
|
// the HTLC should be returned to our settled balance.
|
|
case !isIncoming && htlc.EntryType == Fail:
|
|
*ourBalance += htlc.Amount
|
|
}
|
|
|
|
*removeHeight = nextHeight
|
|
}
|
|
|
|
// generateRemoteHtlcSigJobs generates a series of HTLC signature jobs for the
|
|
// sig pool, along with a channel that if closed, will cancel any jobs after
|
|
// they have been submitted to the sigPool. This method is to be used when
|
|
// generating a new commitment for the remote party. The jobs generated by the
|
|
// signature can be submitted to the sigPool to generate all the signatures
|
|
// asynchronously and in parallel.
|
|
//
|
|
// TODO(roasbeef): all keys will eventually be generated within the commitment
|
|
// itself
|
|
func genRemoteHtlcSigJobs(commitPoint *btcec.PublicKey,
|
|
localChanCfg, remoteChanCfg *channeldb.ChannelConfig,
|
|
remoteCommitView *commitment) ([]signJob, chan struct{}, error) {
|
|
|
|
// First, we'll generate all the keys required to generate the scripts
|
|
// for each HTLC output and transaction.
|
|
//
|
|
// TODO(roabseef): avoid re-calculating, put in commitment struct?
|
|
commitTweak := SingleTweakBytes(commitPoint,
|
|
localChanCfg.PaymentBasePoint)
|
|
revocationKey := DeriveRevocationPubkey(
|
|
localChanCfg.RevocationBasePoint,
|
|
commitPoint,
|
|
)
|
|
remoteDelayKey := TweakPubKey(remoteChanCfg.DelayBasePoint,
|
|
commitPoint)
|
|
|
|
txHash := remoteCommitView.txn.TxHash()
|
|
dustLimit := localChanCfg.DustLimit
|
|
feePerKw := remoteCommitView.feePerKw
|
|
|
|
// With the keys generated, we'll make a slice with enough capacity to
|
|
// hold potentially all the HTLC's. The actual slice may be a bit
|
|
// smaller (than its total capacity) an some HTLC's may be dust.
|
|
numSigs := (len(remoteCommitView.incomingHTLCs) +
|
|
len(remoteCommitView.outgoingHTLCs))
|
|
sigBatch := make([]signJob, 0, numSigs)
|
|
|
|
var err error
|
|
cancelChan := make(chan struct{})
|
|
|
|
// For ech outgoing and incoming HTLC, if the HTLC isn't considered a
|
|
// dust output after taking into account second-level HTLC fees, then a
|
|
// sigJob will be generated and appended to the current batch.
|
|
for _, htlc := range remoteCommitView.incomingHTLCs {
|
|
if htlcIsDust(true, false, feePerKw, htlc.Amount, dustLimit) {
|
|
continue
|
|
}
|
|
|
|
// If the HTLC isn't dust, then we'll create an empty sign job
|
|
// to add to the batch momentarily.
|
|
sigJob := signJob{}
|
|
sigJob.cancel = cancelChan
|
|
sigJob.resp = make(chan signJobResp, 1)
|
|
|
|
// As this is an incoming HTLC and we're sinning the commitment
|
|
// transaction of the remote node, we'll need to generate an
|
|
// HTLC timeout transaction for them. The output of the timeout
|
|
// transaction needs to account for fees, so we'll compute the
|
|
// required fee and output now.
|
|
htlcFee := htlcTimeoutFee(feePerKw)
|
|
outputAmt := htlc.Amount - htlcFee
|
|
|
|
// With the fee calculate, we can properly create the HTLC
|
|
// timeout transaction using the HTLC amount minus the fee.
|
|
op := wire.OutPoint{
|
|
Hash: txHash,
|
|
Index: uint32(htlc.remoteOutputIndex),
|
|
}
|
|
sigJob.tx, err = createHtlcTimeoutTx(op, outputAmt,
|
|
htlc.Timeout, uint32(remoteChanCfg.CsvDelay),
|
|
revocationKey, remoteDelayKey)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
// Finally, we'll generate a sign descriptor to generate a
|
|
// signature to give to the remote party for this commitment
|
|
// transaction. Note we use the raw HTLC amount.
|
|
sigJob.signDesc = SignDescriptor{
|
|
PubKey: localChanCfg.PaymentBasePoint,
|
|
SingleTweak: commitTweak,
|
|
WitnessScript: htlc.theirWitnessScript,
|
|
Output: &wire.TxOut{
|
|
Value: int64(htlc.Amount),
|
|
},
|
|
HashType: txscript.SigHashAll,
|
|
SigHashes: txscript.NewTxSigHashes(sigJob.tx),
|
|
InputIndex: 0,
|
|
}
|
|
sigJob.outputIndex = htlc.remoteOutputIndex
|
|
|
|
sigBatch = append(sigBatch, sigJob)
|
|
}
|
|
for _, htlc := range remoteCommitView.outgoingHTLCs {
|
|
if htlcIsDust(false, false, feePerKw, htlc.Amount, dustLimit) {
|
|
continue
|
|
}
|
|
|
|
sigJob := signJob{}
|
|
sigJob.cancel = cancelChan
|
|
sigJob.resp = make(chan signJobResp, 1)
|
|
|
|
// As this is an outgoing HTLC and we're signing the commitment
|
|
// transaction of the remote node, we'll need to generate an
|
|
// HTLC success transaction for them. The output of the timeout
|
|
// transaction needs to account for fees, so we'll compute the
|
|
// required fee and output now.
|
|
htlcFee := htlcSuccessFee(feePerKw)
|
|
outputAmt := htlc.Amount - htlcFee
|
|
|
|
// With the proper output amount calculated, we can now
|
|
// generate the success transaction using the remote party's
|
|
// CSV delay.
|
|
op := wire.OutPoint{
|
|
Hash: txHash,
|
|
Index: uint32(htlc.remoteOutputIndex),
|
|
}
|
|
sigJob.tx, err = createHtlcSuccessTx(op, outputAmt,
|
|
uint32(remoteChanCfg.CsvDelay), revocationKey,
|
|
remoteDelayKey)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
// Finally, we'll generate a sign descriptor to generate a
|
|
// signature to give to the remote party for this commitment
|
|
// transaction. Note we use the raw HTLC amount.
|
|
sigJob.signDesc = SignDescriptor{
|
|
PubKey: localChanCfg.PaymentBasePoint,
|
|
SingleTweak: commitTweak,
|
|
WitnessScript: htlc.theirWitnessScript,
|
|
Output: &wire.TxOut{
|
|
Value: int64(htlc.Amount),
|
|
},
|
|
HashType: txscript.SigHashAll,
|
|
SigHashes: txscript.NewTxSigHashes(sigJob.tx),
|
|
InputIndex: 0,
|
|
}
|
|
sigJob.outputIndex = htlc.remoteOutputIndex
|
|
|
|
sigBatch = append(sigBatch, sigJob)
|
|
}
|
|
|
|
return sigBatch, cancelChan, nil
|
|
}
|
|
|
|
// SignNextCommitment signs a new commitment which includes any previous
|
|
// unsettled HTLCs, any new HTLCs, and any modifications to prior HTLCs
|
|
// committed in previous commitment updates. Signing a new commitment
|
|
// decrements the available revocation window by 1. After a successful method
|
|
// call, the remote party's commitment chain is extended by a new commitment
|
|
// which includes all updates to the HTLC log prior to this method invocation.
|
|
// The first return parameter it he signature for the commitment transaction
|
|
// itself, while the second parameter is a slice of all HTLC signatures (if
|
|
// any). The HTLC signatures are sorted according to the BIP 69 order of the
|
|
// HTLC's on the commitment transaction.
|
|
func (lc *LightningChannel) SignNextCommitment() (*btcec.Signature, []*btcec.Signature, error) {
|
|
lc.Lock()
|
|
defer lc.Unlock()
|
|
|
|
// If we're awaiting an ACK to a commitment signature, then we're
|
|
// unable to create new states as we don't have any revocations we can
|
|
// use.
|
|
if lc.pendingACK {
|
|
return nil, nil, ErrNoWindow
|
|
}
|
|
|
|
// Before we extend this new commitment to the remote commitment chain,
|
|
// ensure that we aren't violating any of the constraints the remote
|
|
// party set up when we initially set up the channel. If we are, then
|
|
// we'll abort this state transition.
|
|
err := lc.validateCommitmentSanity(lc.remoteUpdateLog.ackedIndex,
|
|
lc.localUpdateLog.logIndex, false, true, true)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
// Grab the next commitment point for the remote party. This well be
|
|
// used within fetchCommitmentView to derive all the keys necessary to
|
|
// construct the commitment state.
|
|
commitPoint := lc.channelState.RemoteNextRevocation
|
|
|
|
// Create a new commitment view which will calculate the evaluated
|
|
// state of the remote node's new commitment including our latest added
|
|
// HTLCs. The view includes the latest balances for both sides on the
|
|
// remote node's chain, and also update the addition height of any new
|
|
// HTLC log entries. When we creating a new remote view, we include
|
|
// _all_ of our changes (pending or committed) but only the remote
|
|
// node's changes up to the last change we've ACK'd.
|
|
newCommitView, err := lc.fetchCommitmentView(true,
|
|
lc.localUpdateLog.logIndex, lc.remoteUpdateLog.ackedIndex,
|
|
commitPoint)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
walletLog.Tracef("ChannelPoint(%v): extending remote chain to height %v",
|
|
lc.channelState.FundingOutpoint, newCommitView.height)
|
|
|
|
walletLog.Tracef("ChannelPoint(%v): remote chain: our_balance=%v, "+
|
|
"their_balance=%v, commit_tx: %v",
|
|
lc.channelState.FundingOutpoint, newCommitView.ourBalance,
|
|
newCommitView.theirBalance,
|
|
newLogClosure(func() string {
|
|
return spew.Sdump(newCommitView.txn)
|
|
}),
|
|
)
|
|
|
|
// With the commitment view constructed, if there are any HTLC's, we'll
|
|
// need to generate signatures of each of them for the remote party's
|
|
// commitment state. We do so in two phases: first we generate and
|
|
// submit the set of signature jobs to the worker pool.
|
|
sigBatch, cancelChan, err := genRemoteHtlcSigJobs(commitPoint,
|
|
lc.localChanCfg, lc.remoteChanCfg, newCommitView,
|
|
)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
lc.sigPool.SubmitSignBatch(sigBatch)
|
|
|
|
// While the jobs are being carried out, we'll Sign their version of
|
|
// the new commitment transaction while we're waiting for the rest of
|
|
// the HTLC signatures to be processed.
|
|
lc.signDesc.SigHashes = txscript.NewTxSigHashes(newCommitView.txn)
|
|
rawSig, err := lc.signer.SignOutputRaw(newCommitView.txn, lc.signDesc)
|
|
if err != nil {
|
|
close(cancelChan)
|
|
return nil, nil, err
|
|
}
|
|
sig, err := btcec.ParseSignature(rawSig, btcec.S256())
|
|
if err != nil {
|
|
close(cancelChan)
|
|
return nil, nil, err
|
|
}
|
|
|
|
// We'll need to send over the signatures to the remote party in the
|
|
// order as they appear on the commitment transaction after BIP 69
|
|
// sorting.
|
|
sortedSigs := sortableSignBatch(sigBatch)
|
|
sort.Sort(sortedSigs)
|
|
|
|
// With the jobs sorted, we'll now iterate through all the responses to
|
|
// gather each of the signatures in order.
|
|
htlcSigs := make([]*btcec.Signature, 0, len(sigBatch))
|
|
for _, htlcSigJob := range sortedSigs {
|
|
jobResp := <-htlcSigJob.resp
|
|
|
|
// If an error occurred, then we'll cancel any other active
|
|
// jobs.
|
|
if jobResp.err != nil {
|
|
close(cancelChan)
|
|
return nil, nil, err
|
|
}
|
|
|
|
htlcSigs = append(htlcSigs, jobResp.sig)
|
|
}
|
|
|
|
// Extend the remote commitment chain by one with the addition of our
|
|
// latest commitment update.
|
|
lc.remoteCommitChain.addCommitment(newCommitView)
|
|
|
|
// If we are the channel initiator then we would have signed any sent
|
|
// fee update at this point, so mark this update as pending ACK, and
|
|
// set pendingFeeUpdate to nil. We can do this since we know we won't
|
|
// sign any new commitment before receiving a revoke_and_ack, because
|
|
// of the revocation window of 1.
|
|
if lc.channelState.IsInitiator {
|
|
lc.pendingAckFeeUpdate = lc.pendingFeeUpdate
|
|
lc.pendingFeeUpdate = nil
|
|
}
|
|
|
|
// As we've just created a new update for the remote commitment chain,
|
|
// we set the bool indicating that we're waiting for an ACK to our new
|
|
// changes.
|
|
lc.pendingACK = true
|
|
|
|
// Additionally, we'll remember our log index at this point, so we can
|
|
// properly track which changes have been ACK'd.
|
|
lc.localUpdateLog.initiateTransition()
|
|
|
|
return sig, htlcSigs, nil
|
|
}
|
|
|
|
// validateCommitmentSanity is used to validate that on current state the commitment
|
|
// transaction is valid in terms of propagating it over Bitcoin network, and
|
|
// also that all outputs are meet Bitcoin spec requirements and they are
|
|
// spendable.
|
|
func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter,
|
|
ourLogCounter uint64, prediction bool, local bool, remote bool) error {
|
|
|
|
// TODO(roasbeef): verify remaining sanity requirements
|
|
htlcCount := 0
|
|
|
|
// If we adding or receiving the htlc we increase the number of htlcs
|
|
// by one in order to not overflow the commitment transaction by
|
|
// insertion.
|
|
if prediction {
|
|
htlcCount++
|
|
}
|
|
|
|
// Run through all the HTLCs that will be covered by this transaction
|
|
// in order to calculate theirs count.
|
|
view := lc.fetchHTLCView(theirLogCounter, ourLogCounter)
|
|
|
|
if remote {
|
|
for _, entry := range view.theirUpdates {
|
|
if entry.EntryType == Add {
|
|
htlcCount++
|
|
}
|
|
}
|
|
for _, entry := range view.ourUpdates {
|
|
if entry.EntryType != Add {
|
|
htlcCount--
|
|
}
|
|
}
|
|
}
|
|
|
|
if local {
|
|
for _, entry := range view.ourUpdates {
|
|
if entry.EntryType == Add {
|
|
htlcCount++
|
|
}
|
|
}
|
|
for _, entry := range view.theirUpdates {
|
|
if entry.EntryType != Add {
|
|
htlcCount--
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we're validating the commitment sanity for HTLC _log_ update by a
|
|
// particular side, then we'll only consider half of the available HTLC
|
|
// bandwidth. However, if we're validating the _creation_ of a new
|
|
// commitment state, then we'll use the full value as the sum of the
|
|
// contribution of both sides shouldn't exceed the max number.
|
|
var maxHTLCNumber int
|
|
if local && remote {
|
|
maxHTLCNumber = MaxHTLCNumber
|
|
} else {
|
|
maxHTLCNumber = MaxHTLCNumber / 2
|
|
}
|
|
|
|
if htlcCount > maxHTLCNumber {
|
|
return ErrMaxHTLCNumber
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// genHtlcSigValidationJobs generates a series of signatures verification jobs
|
|
// meant to verify all the signatures for HTLC's attached to a newly created
|
|
// commitment state. The jobs generated are fully populated, and can be sent
|
|
// directly into the pool of workers.
|
|
func genHtlcSigValidationJobs(localCommitmentView *commitment,
|
|
commitPoint *btcec.PublicKey, htlcSigs []*btcec.Signature,
|
|
localChanCfg, remoteChanCfg *channeldb.ChannelConfig) []verifyJob {
|
|
|
|
// If this new commitment state doesn't have any HTLC's that are to be
|
|
// signed, then we'll return a nil slice.
|
|
if len(htlcSigs) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// First, we'll re-derive the keys necessary to reconstruct the HTLC
|
|
// output and transaction state.
|
|
remoteKey := TweakPubKey(remoteChanCfg.PaymentBasePoint, commitPoint)
|
|
revocationKey := DeriveRevocationPubkey(
|
|
remoteChanCfg.RevocationBasePoint,
|
|
commitPoint,
|
|
)
|
|
localDelayKey := TweakPubKey(localChanCfg.DelayBasePoint,
|
|
commitPoint)
|
|
|
|
txHash := localCommitmentView.txn.TxHash()
|
|
feePerKw := localCommitmentView.feePerKw
|
|
|
|
// With the required state generated, we'll create a slice with large
|
|
// enough capacity to hold verification jobs for all HTLC's in this
|
|
// view. In the case that we have some dust outputs, then the actual
|
|
// length will be smaller than the total capacity.
|
|
numHtlcs := (len(localCommitmentView.incomingHTLCs) +
|
|
len(localCommitmentView.outgoingHTLCs))
|
|
verifyJobs := make([]verifyJob, 0, numHtlcs)
|
|
|
|
// We'll iterate through each output in the commitment transaction,
|
|
// populating the sigHash closure function if it's detected to be an
|
|
// HLTC output. Given the sighash, and the signing key, we'll be able
|
|
// to validate each signature within the worker pool.
|
|
i := 0
|
|
for index, _ := range localCommitmentView.txn.TxOut {
|
|
var sigHash func() ([]byte, error)
|
|
|
|
outputIndex := int32(index)
|
|
switch {
|
|
|
|
// If this output index is found within the incoming HTLC index,
|
|
// then this means that we need to generate an HTLC success
|
|
// transaction in order to validate the signature.
|
|
case localCommitmentView.incomingHTLCIndex[outputIndex] != nil:
|
|
htlc := localCommitmentView.incomingHTLCIndex[outputIndex]
|
|
|
|
sigHash = func() ([]byte, error) {
|
|
op := wire.OutPoint{
|
|
Hash: txHash,
|
|
Index: uint32(htlc.localOutputIndex),
|
|
}
|
|
|
|
htlcFee := htlcSuccessFee(feePerKw)
|
|
outputAmt := htlc.Amount - htlcFee
|
|
|
|
successTx, err := createHtlcSuccessTx(op,
|
|
outputAmt, uint32(localChanCfg.CsvDelay),
|
|
revocationKey, localDelayKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
hashCache := txscript.NewTxSigHashes(successTx)
|
|
sigHash, err := txscript.CalcWitnessSigHash(
|
|
htlc.ourWitnessScript, hashCache,
|
|
txscript.SigHashAll, successTx, 0,
|
|
int64(htlc.Amount))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return sigHash, nil
|
|
}
|
|
|
|
// With the sighash generated, we'll also store the
|
|
// signature so it can be written to disk if this state
|
|
// is valid.
|
|
htlc.sig = htlcSigs[i]
|
|
|
|
// Otherwise, if this is an outgoing HTLC, then we'll need to
|
|
// generate a timeout transaction so we can verify the
|
|
// signature presented.
|
|
case localCommitmentView.outgoignHTLCIndex[outputIndex] != nil:
|
|
htlc := localCommitmentView.outgoignHTLCIndex[outputIndex]
|
|
|
|
sigHash = func() ([]byte, error) {
|
|
op := wire.OutPoint{
|
|
Hash: txHash,
|
|
Index: uint32(htlc.localOutputIndex),
|
|
}
|
|
|
|
htlcFee := htlcTimeoutFee(feePerKw)
|
|
outputAmt := htlc.Amount - htlcFee
|
|
|
|
timeoutTx, err := createHtlcTimeoutTx(op,
|
|
outputAmt, htlc.Timeout,
|
|
uint32(localChanCfg.CsvDelay),
|
|
revocationKey, localDelayKey,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
hashCache := txscript.NewTxSigHashes(timeoutTx)
|
|
sigHash, err := txscript.CalcWitnessSigHash(
|
|
htlc.ourWitnessScript, hashCache,
|
|
txscript.SigHashAll, timeoutTx, 0,
|
|
int64(htlc.Amount),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return sigHash, nil
|
|
}
|
|
|
|
// With the sighash generated, we'll also store the
|
|
// signature so it can be written to disk if this state
|
|
// is valid.
|
|
htlc.sig = htlcSigs[i]
|
|
|
|
default:
|
|
continue
|
|
}
|
|
|
|
verifyJobs = append(verifyJobs, verifyJob{
|
|
pubKey: remoteKey,
|
|
sig: htlcSigs[i],
|
|
sigHash: sigHash,
|
|
})
|
|
|
|
i++
|
|
}
|
|
|
|
return verifyJobs
|
|
}
|
|
|
|
// ReceiveNewCommitment process a signature for a new commitment state sent by
|
|
// the remote party. This method will should be called in response to the
|
|
// remote party initiating a new change, or when the remote party sends a
|
|
// signature fully accepting a new state we've initiated. If we are able to
|
|
// successfully validate the signature, then the generated commitment is added
|
|
// to our local commitment chain. Once we send a revocation for our prior
|
|
// state, then this newly added commitment becomes our current accepted channel
|
|
// state.
|
|
func (lc *LightningChannel) ReceiveNewCommitment(commitSig *btcec.Signature,
|
|
htlcSigs []*btcec.Signature) error {
|
|
|
|
lc.Lock()
|
|
defer lc.Unlock()
|
|
|
|
// Ensure that this new local update from the remote node respects all
|
|
// the constraints we specified during initial channel setup. If not,
|
|
// then we'll abort the channel as they've violated our constraints.
|
|
err := lc.validateCommitmentSanity(lc.remoteUpdateLog.logIndex,
|
|
lc.localUpdateLog.ackedIndex, false, true, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// We're receiving a new commitment which attempts to extend our local
|
|
// commitment chain height by one, so fetch the proper commitment point
|
|
// as this well be needed to derive the keys required to construct the
|
|
// commitment.
|
|
nextHeight := lc.currentHeight + 1
|
|
commitSecret, err := lc.channelState.RevocationProducer.AtIndex(nextHeight)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
commitPoint := ComputeCommitmentPoint(commitSecret[:])
|
|
|
|
// With the current commitment point re-calculated, construct the new
|
|
// commitment view which includes all the entries we know of in their
|
|
// HTLC log, and up to ourLogIndex in our HTLC log.
|
|
localCommitmentView, err := lc.fetchCommitmentView(false,
|
|
lc.localUpdateLog.ackedIndex, lc.remoteUpdateLog.logIndex,
|
|
commitPoint)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
walletLog.Tracef("ChannelPoint(%v): extending local chain to height %v",
|
|
lc.channelState.FundingOutpoint, localCommitmentView.height)
|
|
|
|
walletLog.Tracef("ChannelPoint(%v): local chain: our_balance=%v, "+
|
|
"their_balance=%v, commit_tx: %v", lc.channelState.FundingOutpoint,
|
|
localCommitmentView.ourBalance, localCommitmentView.theirBalance,
|
|
newLogClosure(func() string {
|
|
return spew.Sdump(localCommitmentView.txn)
|
|
}),
|
|
)
|
|
|
|
// Construct the sighash of the commitment transaction corresponding to
|
|
// this newly proposed state update.
|
|
localCommitTx := localCommitmentView.txn
|
|
multiSigScript := lc.FundingWitnessScript
|
|
hashCache := txscript.NewTxSigHashes(localCommitTx)
|
|
sigHash, err := txscript.CalcWitnessSigHash(multiSigScript, hashCache,
|
|
txscript.SigHashAll, localCommitTx, 0,
|
|
int64(lc.channelState.Capacity))
|
|
if err != nil {
|
|
// TODO(roasbeef): fetchview has already mutated the HTLCs...
|
|
// * need to either roll-back, or make pure
|
|
return err
|
|
}
|
|
|
|
// As an optimization, we'll generate a series of jobs for the worker
|
|
// pool to verify each of the HTLc signatures presented. Once
|
|
// generated, we'll submit these jobs to the worker pool.
|
|
verifyJobs := genHtlcSigValidationJobs(localCommitmentView,
|
|
commitPoint,
|
|
htlcSigs, lc.localChanCfg, lc.remoteChanCfg)
|
|
cancelChan := make(chan struct{})
|
|
verifyResps := lc.sigPool.SubmitVerifyBatch(verifyJobs, cancelChan)
|
|
|
|
// While the HTLC verification jobs are proceeding asynchronously,
|
|
// we'll ensure that the newly constructed commitment state has a valid
|
|
// signature.
|
|
verifyKey := btcec.PublicKey{
|
|
X: lc.remoteChanCfg.MultiSigKey.X,
|
|
Y: lc.remoteChanCfg.MultiSigKey.Y,
|
|
Curve: btcec.S256(),
|
|
}
|
|
if !commitSig.Verify(sigHash, &verifyKey) {
|
|
close(cancelChan)
|
|
return fmt.Errorf("invalid commitment signature")
|
|
}
|
|
|
|
// With the primary commitment transaction validated, we'll check each
|
|
// of the HTLC validation jobs.
|
|
for i := 0; i < len(verifyJobs); i++ {
|
|
// In the case that a single signature is invalid, we'll exit
|
|
// early and cancel all the outstanding verification jobs.
|
|
if err := <-verifyResps; err != nil {
|
|
close(cancelChan)
|
|
return fmt.Errorf("invalid htlc signature: %v", err)
|
|
}
|
|
}
|
|
|
|
// The signature checks out, so we can now add the new commitment to
|
|
// our local commitment chain.
|
|
localCommitmentView.sig = commitSig.Serialize()
|
|
lc.localCommitChain.addCommitment(localCommitmentView)
|
|
|
|
// If we are not channel initiator, then the commitment just received
|
|
// would've signed any received fee update since last commitment. Mark
|
|
// any such fee update as pending ACK (so we remember to ACK it on our
|
|
// next commitment), and set pendingFeeUpdate to nil. We can do this
|
|
// since we won't receive any new commitment before ACKing.
|
|
if !lc.channelState.IsInitiator {
|
|
lc.pendingAckFeeUpdate = lc.pendingFeeUpdate
|
|
lc.pendingFeeUpdate = nil
|
|
}
|
|
|
|
// Finally we'll keep track of the current pending index for the remote
|
|
// party so we can ACK up to this value once we revoke our current
|
|
// commitment.
|
|
lc.remoteUpdateLog.initiateTransition()
|
|
|
|
return nil
|
|
}
|
|
|
|
// FullySynced returns a boolean value reflecting if both commitment chains
|
|
// (remote+local) are fully in sync. Both commitment chains are fully in sync
|
|
// if the tip of each chain includes the latest committed changes from both
|
|
// sides.
|
|
func (lc *LightningChannel) FullySynced() bool {
|
|
lc.RLock()
|
|
defer lc.RUnlock()
|
|
|
|
oweCommitment := (lc.localCommitChain.tip().height >
|
|
lc.remoteCommitChain.tip().height)
|
|
|
|
localUpdatesSynced := (lc.localCommitChain.tip().ourMessageIndex ==
|
|
lc.remoteCommitChain.tip().ourMessageIndex)
|
|
|
|
remoteUpdatesSynced := (lc.localCommitChain.tip().theirMessageIndex ==
|
|
lc.remoteCommitChain.tip().theirMessageIndex)
|
|
|
|
return !oweCommitment && localUpdatesSynced && remoteUpdatesSynced
|
|
}
|
|
|
|
// RevokeCurrentCommitment revokes the next lowest unrevoked commitment
|
|
// transaction in the local commitment chain. As a result the edge of our
|
|
// revocation window is extended by one, and the tail of our local commitment
|
|
// chain is advanced by a single commitment. This now lowest unrevoked
|
|
// commitment becomes our currently accepted state within the channel.
|
|
func (lc *LightningChannel) RevokeCurrentCommitment() (*lnwire.RevokeAndAck, error) {
|
|
lc.Lock()
|
|
defer lc.Unlock()
|
|
|
|
// Now that we've accept a new state transition, we send the remote
|
|
// party the revocation for our current commitment state.
|
|
revocationMsg := &lnwire.RevokeAndAck{}
|
|
commitSecret, err := lc.channelState.RevocationProducer.AtIndex(
|
|
lc.currentHeight,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
copy(revocationMsg.Revocation[:], commitSecret[:])
|
|
|
|
// Along with this revocation, we'll also send the _next_ commitment
|
|
// point that the remote party should use to create our next commitment
|
|
// transaction. We use a +2 here as we already gave them a look ahead
|
|
// of size one after the FundingLocked message was sent:
|
|
//
|
|
// 0: current revocation, 1: their "next" revocation, 2: this revocation
|
|
//
|
|
// We're revoking the current revocation. Once they receive this
|
|
// message they'll set the "current" revocation for us to their stored
|
|
// "next" revocation, and this revocation will become their new "next"
|
|
// revocation.
|
|
//
|
|
// Put simply in the window slides to the left by one.
|
|
nextCommitSecret, err := lc.channelState.RevocationProducer.AtIndex(
|
|
lc.currentHeight + 2,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
revocationMsg.NextRevocationKey = ComputeCommitmentPoint(
|
|
nextCommitSecret[:],
|
|
)
|
|
|
|
walletLog.Tracef("ChannelPoint(%v): revoking height=%v, now at height=%v",
|
|
lc.channelState.FundingOutpoint, lc.localCommitChain.tail().height,
|
|
lc.currentHeight+1)
|
|
|
|
// Advance our tail, as we've revoked our previous state.
|
|
lc.localCommitChain.advanceTail()
|
|
lc.currentHeight++
|
|
|
|
// Additionally, generate a channel delta for this state transition for
|
|
// persistent storage.
|
|
tail := lc.localCommitChain.tail()
|
|
delta, err := tail.toChannelDelta(true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = lc.channelState.UpdateCommitment(tail.txn, tail.sig, delta)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
walletLog.Tracef("ChannelPoint(%v): state transition accepted: "+
|
|
"our_balance=%v, their_balance=%v",
|
|
lc.channelState.FundingOutpoint, tail.ourBalance,
|
|
tail.theirBalance)
|
|
|
|
// In the process of revoking our current commitment, we've also
|
|
// implicitly ACK'd their set of pending changes that arrived before
|
|
// the signature the triggered this revocation. So we'll move up their
|
|
// ACK'd index within the log to right at this set of pending changes.
|
|
lc.remoteUpdateLog.ackTransition()
|
|
|
|
revocationMsg.ChanID = lnwire.NewChanIDFromOutPoint(
|
|
&lc.channelState.FundingOutpoint,
|
|
)
|
|
|
|
return revocationMsg, nil
|
|
}
|
|
|
|
// LocalAvailableBalance returns the amount of available money which might be
|
|
// proceed by this channel at the specific point of time.
|
|
func (lc *LightningChannel) LocalAvailableBalance() btcutil.Amount {
|
|
lc.Lock()
|
|
defer lc.Unlock()
|
|
|
|
return lc.availableLocalBalance
|
|
}
|
|
|
|
// ReceiveRevocation processes a revocation sent by the remote party for the
|
|
// lowest unrevoked commitment within their commitment chain. We receive a
|
|
// revocation either during the initial session negotiation wherein revocation
|
|
// windows are extended, or in response to a state update that we initiate. If
|
|
// successful, then the remote commitment chain is advanced by a single
|
|
// commitment, and a log compaction is attempted. In addition, a slice of
|
|
// HTLC's which can be forwarded upstream are returned.
|
|
func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) ([]*PaymentDescriptor, error) {
|
|
lc.Lock()
|
|
defer lc.Unlock()
|
|
|
|
// Now that we've received a new revocation from the remote party,
|
|
// we'll toggle our pendingACk bool to indicate that we can create a
|
|
// new commitment state after we finish processing this revocation.
|
|
lc.pendingACK = false
|
|
|
|
// Ensure that the new pre-image can be placed in preimage store.
|
|
store := lc.channelState.RevocationStore
|
|
revocation, err := chainhash.NewHash(revMsg.Revocation[:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := store.AddNextEntry(revocation); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Verify that if we use the commitment point computed based off of the
|
|
// revealed secret to derive a revocation key with our revocation base
|
|
// point, then it matches the current revocation of the remote party.
|
|
currentCommitPoint := lc.channelState.RemoteCurrentRevocation
|
|
derivedCommitPoint := ComputeCommitmentPoint(revMsg.Revocation[:])
|
|
if !derivedCommitPoint.IsEqual(currentCommitPoint) {
|
|
return nil, fmt.Errorf("revocation key mismatch")
|
|
}
|
|
|
|
// Now that we've verified that the prior commitment has been properly
|
|
// revoked, we'll advance the revocation state we track for the remote
|
|
// party: the new current revocation is what was previously the next
|
|
// revocation, and the new next revocation is set to the key included
|
|
// in the message.
|
|
lc.channelState.RemoteCurrentRevocation = lc.channelState.RemoteNextRevocation
|
|
lc.channelState.RemoteNextRevocation = revMsg.NextRevocationKey
|
|
|
|
walletLog.Tracef("ChannelPoint(%v): remote party accepted state transition, "+
|
|
"revoked height %v, now at %v", lc.channelState.FundingOutpoint,
|
|
lc.remoteCommitChain.tail().height,
|
|
lc.remoteCommitChain.tail().height+1)
|
|
|
|
// At this point, the revocation has been accepted, and we've rotated
|
|
// the current revocation key+hash for the remote party. Therefore we
|
|
// sync now to ensure the revocation producer state is consistent with
|
|
// the current commitment height.
|
|
tail := lc.remoteCommitChain.tail()
|
|
delta, err := tail.toChannelDelta(false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := lc.channelState.AppendToRevocationLog(delta); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Since they revoked the current lowest height in their commitment
|
|
// chain, we can advance their chain by a single commitment.
|
|
lc.remoteCommitChain.advanceTail()
|
|
|
|
remoteChainTail := lc.remoteCommitChain.tail().height
|
|
localChainTail := lc.localCommitChain.tail().height
|
|
|
|
// Now that we've verified the revocation update the state of the HTLC
|
|
// log as we may be able to prune portions of it now, and update their
|
|
// balance.
|
|
var htlcsToForward []*PaymentDescriptor
|
|
for e := lc.remoteUpdateLog.Front(); e != nil; e = e.Next() {
|
|
htlc := e.Value.(*PaymentDescriptor)
|
|
|
|
if htlc.isForwarded {
|
|
continue
|
|
}
|
|
|
|
// TODO(roasbeef): re-visit after adding persistence to HTLCs
|
|
// * either record add height, or set to N - 1
|
|
uncomitted := (htlc.addCommitHeightRemote == 0 ||
|
|
htlc.addCommitHeightLocal == 0)
|
|
if htlc.EntryType == Add && uncomitted {
|
|
continue
|
|
}
|
|
|
|
if htlc.EntryType == Add &&
|
|
remoteChainTail >= htlc.addCommitHeightRemote &&
|
|
localChainTail >= htlc.addCommitHeightLocal {
|
|
|
|
htlc.isForwarded = true
|
|
htlcsToForward = append(htlcsToForward, htlc)
|
|
} else if htlc.EntryType != Add &&
|
|
remoteChainTail >= htlc.removeCommitHeightRemote &&
|
|
localChainTail >= htlc.removeCommitHeightLocal {
|
|
|
|
htlc.isForwarded = true
|
|
htlcsToForward = append(htlcsToForward, htlc)
|
|
}
|
|
}
|
|
|
|
// As we've just completed a new state transition, attempt to see if we
|
|
// can remove any entries from the update log which have been removed
|
|
// from the PoV of both commitment chains.
|
|
compactLogs(lc.localUpdateLog, lc.remoteUpdateLog,
|
|
localChainTail, remoteChainTail)
|
|
|
|
// As a final step, now that we've received an ACK for our last batch
|
|
// of pending changes, we'll update our local ACK'd index to the now
|
|
// commitment index, and reset our pendingACKIndex.
|
|
lc.localUpdateLog.ackTransition()
|
|
|
|
return htlcsToForward, nil
|
|
}
|
|
|
|
// NextRevocationKey returns the commitment point for the _next_ commitment
|
|
// height. The pubkey returned by this function is required by the remote party
|
|
// along with their revocation base to to extend our commitment chain with a
|
|
// new commitment.
|
|
func (lc *LightningChannel) NextRevocationkey() (*btcec.PublicKey, error) {
|
|
lc.RLock()
|
|
defer lc.RUnlock()
|
|
|
|
nextHeight := lc.currentHeight + 1
|
|
revocation, err := lc.channelState.RevocationProducer.AtIndex(nextHeight)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return ComputeCommitmentPoint(revocation[:]), nil
|
|
}
|
|
|
|
// InitNextRevocation inserts the passed commitment point as the _next_
|
|
// revocation to be used when created a new commitment state for the remote
|
|
// party. This function MUST be called before the channel can accept or propose
|
|
// any new states.
|
|
func (lc *LightningChannel) InitNextRevocation(revKey *btcec.PublicKey) error {
|
|
lc.Lock()
|
|
defer lc.Unlock()
|
|
|
|
return lc.channelState.InsertNextRevocation(revKey)
|
|
}
|
|
|
|
// AddHTLC adds an HTLC to the state machine's local update log. This method
|
|
// should be called when preparing to send an outgoing HTLC.
|
|
func (lc *LightningChannel) AddHTLC(htlc *lnwire.UpdateAddHTLC) (uint64, error) {
|
|
lc.Lock()
|
|
defer lc.Unlock()
|
|
|
|
if err := lc.validateCommitmentSanity(lc.remoteUpdateLog.logIndex,
|
|
lc.localUpdateLog.logIndex, true, true, false); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if lc.availableLocalBalance < htlc.Amount {
|
|
return 0, ErrInsufficientBalance
|
|
}
|
|
lc.availableLocalBalance -= htlc.Amount
|
|
|
|
pd := &PaymentDescriptor{
|
|
EntryType: Add,
|
|
RHash: PaymentHash(htlc.PaymentHash),
|
|
Timeout: htlc.Expiry,
|
|
Amount: htlc.Amount,
|
|
Index: lc.localUpdateLog.logIndex,
|
|
}
|
|
|
|
lc.localUpdateLog.appendUpdate(pd)
|
|
|
|
return pd.Index, nil
|
|
}
|
|
|
|
// ReceiveHTLC adds an HTLC to the state machine's remote update log. This
|
|
// method should be called in response to receiving a new HTLC from the remote
|
|
// party.
|
|
func (lc *LightningChannel) ReceiveHTLC(htlc *lnwire.UpdateAddHTLC) (uint64, error) {
|
|
lc.Lock()
|
|
defer lc.Unlock()
|
|
|
|
if err := lc.validateCommitmentSanity(lc.remoteUpdateLog.logIndex,
|
|
lc.localUpdateLog.logIndex, true, false, true); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
pd := &PaymentDescriptor{
|
|
EntryType: Add,
|
|
RHash: PaymentHash(htlc.PaymentHash),
|
|
Timeout: htlc.Expiry,
|
|
Amount: htlc.Amount,
|
|
Index: lc.remoteUpdateLog.logIndex,
|
|
}
|
|
|
|
lc.remoteUpdateLog.appendUpdate(pd)
|
|
|
|
lc.rHashMap[pd.RHash] = append(lc.rHashMap[pd.RHash], pd)
|
|
|
|
return pd.Index, nil
|
|
}
|
|
|
|
// SettleHTLC attempts to settle an existing outstanding received HTLC. The
|
|
// remote log index of the HTLC settled is returned in order to facilitate
|
|
// creating the corresponding wire message. In the case the supplied preimage
|
|
// is invalid, an error is returned.
|
|
func (lc *LightningChannel) SettleHTLC(preimage [32]byte) (uint64, error) {
|
|
lc.Lock()
|
|
defer lc.Unlock()
|
|
|
|
paymentHash := sha256.Sum256(preimage[:])
|
|
targetHTLCs, ok := lc.rHashMap[paymentHash]
|
|
if !ok {
|
|
return 0, fmt.Errorf("invalid payment hash(%v)",
|
|
hex.EncodeToString(paymentHash[:]))
|
|
}
|
|
targetHTLC := targetHTLCs[0]
|
|
|
|
pd := &PaymentDescriptor{
|
|
Amount: targetHTLC.Amount,
|
|
RPreimage: preimage,
|
|
Index: lc.localUpdateLog.logIndex,
|
|
ParentIndex: targetHTLC.Index,
|
|
EntryType: Settle,
|
|
}
|
|
|
|
lc.localUpdateLog.appendUpdate(pd)
|
|
|
|
lc.rHashMap[paymentHash][0] = nil
|
|
lc.rHashMap[paymentHash] = lc.rHashMap[paymentHash][1:]
|
|
if len(lc.rHashMap[paymentHash]) == 0 {
|
|
delete(lc.rHashMap, paymentHash)
|
|
}
|
|
|
|
lc.availableLocalBalance += pd.Amount
|
|
return targetHTLC.Index, nil
|
|
}
|
|
|
|
// ReceiveHTLCSettle attempts to settle an existing outgoing HTLC indexed by an
|
|
// index into the local log. If the specified index doesn't exist within the
|
|
// log, and error is returned. Similarly if the preimage is invalid w.r.t to
|
|
// the referenced of then a distinct error is returned.
|
|
func (lc *LightningChannel) ReceiveHTLCSettle(preimage [32]byte, logIndex uint64) error {
|
|
lc.Lock()
|
|
defer lc.Unlock()
|
|
|
|
paymentHash := sha256.Sum256(preimage[:])
|
|
htlc := lc.localUpdateLog.lookup(logIndex)
|
|
if htlc == nil {
|
|
return fmt.Errorf("non existant log entry")
|
|
}
|
|
|
|
if !bytes.Equal(htlc.RHash[:], paymentHash[:]) {
|
|
return fmt.Errorf("invalid payment hash(%v)",
|
|
hex.EncodeToString(paymentHash[:]))
|
|
}
|
|
|
|
pd := &PaymentDescriptor{
|
|
Amount: htlc.Amount,
|
|
RPreimage: preimage,
|
|
ParentIndex: htlc.Index,
|
|
RHash: htlc.RHash,
|
|
Index: lc.remoteUpdateLog.logIndex,
|
|
EntryType: Settle,
|
|
}
|
|
|
|
lc.remoteUpdateLog.appendUpdate(pd)
|
|
return nil
|
|
}
|
|
|
|
// FailHTLC attempts to fail a targeted HTLC by its payment hash, inserting an
|
|
// entry which will remove the target log entry within the next commitment
|
|
// update. This method is intended to be called in order to cancel in
|
|
// _incoming_ HTLC.
|
|
func (lc *LightningChannel) FailHTLC(rHash [32]byte) (uint64, error) {
|
|
lc.Lock()
|
|
defer lc.Unlock()
|
|
|
|
addEntries, ok := lc.rHashMap[rHash]
|
|
if !ok {
|
|
return 0, fmt.Errorf("unable to find HTLC to fail")
|
|
}
|
|
addEntry := addEntries[0]
|
|
|
|
pd := &PaymentDescriptor{
|
|
Amount: addEntry.Amount,
|
|
RHash: addEntry.RHash,
|
|
ParentIndex: addEntry.Index,
|
|
Index: lc.localUpdateLog.logIndex,
|
|
EntryType: Fail,
|
|
}
|
|
|
|
lc.localUpdateLog.appendUpdate(pd)
|
|
|
|
lc.rHashMap[rHash][0] = nil
|
|
lc.rHashMap[rHash] = lc.rHashMap[rHash][1:]
|
|
if len(lc.rHashMap[rHash]) == 0 {
|
|
delete(lc.rHashMap, rHash)
|
|
}
|
|
|
|
return addEntry.Index, nil
|
|
}
|
|
|
|
// ReceiveFailHTLC attempts to cancel a targeted HTLC by its log index,
|
|
// inserting an entry which will remove the target log entry within the next
|
|
// commitment update. This method should be called in response to the upstream
|
|
// party cancelling an outgoing HTLC.
|
|
func (lc *LightningChannel) ReceiveFailHTLC(logIndex uint64) error {
|
|
lc.Lock()
|
|
defer lc.Unlock()
|
|
|
|
htlc := lc.localUpdateLog.lookup(logIndex)
|
|
if htlc == nil {
|
|
return fmt.Errorf("unable to find HTLC to fail")
|
|
}
|
|
|
|
pd := &PaymentDescriptor{
|
|
Amount: htlc.Amount,
|
|
RHash: htlc.RHash,
|
|
ParentIndex: htlc.Index,
|
|
Index: lc.remoteUpdateLog.logIndex,
|
|
EntryType: Fail,
|
|
}
|
|
|
|
lc.remoteUpdateLog.appendUpdate(pd)
|
|
lc.availableLocalBalance += pd.Amount
|
|
return nil
|
|
}
|
|
|
|
// ChannelPoint returns the outpoint of the original funding transaction which
|
|
// created this active channel. This outpoint is used throughout various
|
|
// subsystems to uniquely identify an open channel.
|
|
func (lc *LightningChannel) ChannelPoint() *wire.OutPoint {
|
|
lc.RLock()
|
|
defer lc.RUnlock()
|
|
|
|
return &lc.channelState.FundingOutpoint
|
|
}
|
|
|
|
// ShortChanID returns the short channel ID for the channel. The short channel
|
|
// ID encodes the exact location in the main chain that the original
|
|
// funding output can be found.
|
|
func (lc *LightningChannel) ShortChanID() lnwire.ShortChannelID {
|
|
lc.RLock()
|
|
defer lc.RUnlock()
|
|
|
|
return lc.channelState.ShortChanID
|
|
}
|
|
|
|
// genHtlcScript generates the proper P2WSH public key scripts for the
|
|
// HTLC output modified by two-bits denoting if this is an incoming HTLC, and
|
|
// if the HTLC is being applied to their commitment transaction or ours.
|
|
func (lc *LightningChannel) genHtlcScript(isIncoming, ourCommit bool,
|
|
timeout uint32, rHash [32]byte, localKey, remoteKey *btcec.PublicKey,
|
|
revocationKey *btcec.PublicKey) ([]byte, []byte, error) {
|
|
|
|
var (
|
|
witnessScript []byte
|
|
err error
|
|
)
|
|
|
|
// Generate the proper redeem scripts for the HTLC output modified by
|
|
// two-bits denoting if this is an incoming HTLC, and if the HTLC is
|
|
// being applied to their commitment transaction or ours.
|
|
switch {
|
|
// The HTLC is paying to us, and being applied to our commitment
|
|
// transaction. So we need to use the receiver's version of HTLC the
|
|
// script.
|
|
case isIncoming && ourCommit:
|
|
witnessScript, err = receiverHTLCScript(timeout, remoteKey,
|
|
localKey, revocationKey, rHash[:])
|
|
|
|
// We're being paid via an HTLC by the remote party, and the HTLC is
|
|
// being added to their commitment transaction, so we use the sender's
|
|
// version of the HTLC script.
|
|
case isIncoming && !ourCommit:
|
|
witnessScript, err = senderHTLCScript(remoteKey, localKey,
|
|
revocationKey, rHash[:])
|
|
|
|
// We're sending an HTLC which is being added to our commitment
|
|
// transaction. Therefore, we need to use the sender's version of the
|
|
// HTLC script.
|
|
case !isIncoming && ourCommit:
|
|
witnessScript, err = senderHTLCScript(localKey, remoteKey,
|
|
revocationKey, rHash[:])
|
|
|
|
// Finally, we're paying the remote party via an HTLC, which is being
|
|
// added to their commitment transaction. Therefore, we use the
|
|
// receiver's version of the HTLC script.
|
|
case !isIncoming && !ourCommit:
|
|
witnessScript, err = receiverHTLCScript(timeout, localKey,
|
|
remoteKey, revocationKey, rHash[:])
|
|
}
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
// Now that we have the redeem scripts, create the P2WSH public key
|
|
// script for the output itself.
|
|
htlcP2WSH, err := witnessScriptHash(witnessScript)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return htlcP2WSH, witnessScript, nil
|
|
}
|
|
|
|
// addHTLC adds a new HTLC to the passed commitment transaction. One of four
|
|
// full scripts will be generated for the HTLC output depending on if the HTLC
|
|
// is incoming and if it's being applied to our commitment transaction or that
|
|
// of the remote node's. Additionally, in order to be able to efficiently
|
|
// locate the added HTLC on the commitment transaction from the
|
|
// PaymentDescriptor that generated it, the generated script is stored within
|
|
// the descriptor itself.
|
|
func (lc *LightningChannel) addHTLC(commitTx *wire.MsgTx, ourCommit bool,
|
|
isIncoming bool, paymentDesc *PaymentDescriptor,
|
|
localKey, remoteKey, revocationKey *btcec.PublicKey) error {
|
|
|
|
timeout := paymentDesc.Timeout
|
|
rHash := paymentDesc.RHash
|
|
|
|
p2wsh, witnessScript, err := lc.genHtlcScript(isIncoming,
|
|
ourCommit, timeout, rHash, localKey, remoteKey, revocationKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Add the new HTLC outputs to the respective commitment transactions.
|
|
amountPending := int64(paymentDesc.Amount)
|
|
commitTx.AddTxOut(wire.NewTxOut(amountPending, p2wsh))
|
|
|
|
// Store the pkScript of this particular PaymentDescriptor so we can
|
|
// quickly locate it within the commitment transaction later.
|
|
if ourCommit {
|
|
paymentDesc.ourPkScript = p2wsh
|
|
paymentDesc.ourWitnessScript = witnessScript
|
|
} else {
|
|
paymentDesc.theirPkScript = p2wsh
|
|
paymentDesc.theirWitnessScript = witnessScript
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// getSignedCommitTx function take the latest commitment transaction and populate
|
|
// it with witness data.
|
|
func (lc *LightningChannel) getSignedCommitTx() (*wire.MsgTx, error) {
|
|
// Fetch the current commitment transaction, along with their signature
|
|
// for the transaction.
|
|
commitTx := lc.channelState.CommitTx
|
|
theirSig := append(lc.channelState.CommitSig, byte(txscript.SigHashAll))
|
|
|
|
// With this, we then generate the full witness so the caller can
|
|
// broadcast a fully signed transaction.
|
|
lc.signDesc.SigHashes = txscript.NewTxSigHashes(&commitTx)
|
|
ourSigRaw, err := lc.signer.SignOutputRaw(&commitTx, lc.signDesc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ourSig := append(ourSigRaw, byte(txscript.SigHashAll))
|
|
|
|
// With the final signature generated, create the witness stack
|
|
// required to spend from the multi-sig output.
|
|
ourKey := lc.localChanCfg.MultiSigKey.SerializeCompressed()
|
|
theirKey := lc.remoteChanCfg.MultiSigKey.SerializeCompressed()
|
|
|
|
commitTx.TxIn[0].Witness = SpendMultiSig(lc.FundingWitnessScript, ourKey,
|
|
ourSig, theirKey, theirSig)
|
|
|
|
return &commitTx, nil
|
|
}
|
|
|
|
// UnilateralCloseSummary describes the details of a detected unilateral
|
|
// channel closure. This includes the information about with which
|
|
// transactions, and block the channel was unilaterally closed, as well as
|
|
// summarization details concerning the _state_ of the channel at the point of
|
|
// channel closure. Additionally, if we had a commitment output above dust on
|
|
// the remote party's commitment transaction, the necessary a SignDescriptor
|
|
// with the material necessary to seep the output are returned. Finally, if we
|
|
// had any outgoing HTLC's within the commitment transaction, then an
|
|
// OutgoingHtlcResolution for each output will included.
|
|
type UnilateralCloseSummary struct {
|
|
// SpendDetail is a struct that describes how and when the commitment
|
|
// output was spent.
|
|
*chainntnfs.SpendDetail
|
|
|
|
// ChannelCloseSummary is a struct describing the final state of the
|
|
// channel and in which state is was closed.
|
|
channeldb.ChannelCloseSummary
|
|
|
|
// SelfOutPoint is the full outpoint that points to our non-delayed
|
|
// pay-to-self output within the commitment transaction of the remote
|
|
// party.
|
|
SelfOutPoint *wire.OutPoint
|
|
|
|
// SelfOutputSignDesc is a fully populated sign descriptor capable of
|
|
// generating a valid signature to sweep the output paying to us
|
|
SelfOutputSignDesc *SignDescriptor
|
|
|
|
// HtlcResolutions is a slice of HTLC resolutions which allows the
|
|
// local node to sweep any outgoing HTLC"s after the timeout period has
|
|
// passed.
|
|
HtlcResolutions []OutgoingHtlcResolution
|
|
}
|
|
|
|
// OutgoingHtlcResolution houses the information necessary to sweep any outging
|
|
// HTLC's after their contract has expired. This struct will be needed in one
|
|
// of tow cases: the local party force closes the commitment transaction or the
|
|
// remote party unilaterally closes with their version of the commitment
|
|
// transaction.
|
|
type OutgoingHtlcResolution struct {
|
|
// Expiry the absolute timeout of the HTLC. This value is expressed in
|
|
// block height, meaning after this height the HLTC can be swept.
|
|
Expiry uint32
|
|
|
|
// SignedTimeoutTx is the fully signed HTLC timeout transaction. This
|
|
// must be broadcast immediately after timeout has passed. Once this
|
|
// has been confirmed, the HTLC output will transition into the
|
|
// delay+claim state.
|
|
SignedTimeoutTx *wire.MsgTx
|
|
|
|
// SweepSignDesc is a sign descriptor that has been populated with the
|
|
// necessary items required to spend the sole output of the above
|
|
// transaction.
|
|
SweepSignDesc SignDescriptor
|
|
}
|
|
|
|
// newHtlcResolution generates a new HTLC resolution capable of allowing the
|
|
// caller to sweep an outgoing HTLC present on either their, or the remote
|
|
// party's commitment transaction.
|
|
func newHtlcResolution(signer Signer, localChanCfg *channeldb.ChannelConfig,
|
|
commitHash chainhash.Hash, htlc *channeldb.HTLC, commitTweak []byte,
|
|
delayKey, localKey, remoteKey *btcec.PublicKey, revokeKey *btcec.PublicKey,
|
|
feePewKw, dustLimit btcutil.Amount) (*OutgoingHtlcResolution, error) {
|
|
|
|
op := wire.OutPoint{
|
|
Hash: commitHash,
|
|
Index: uint32(htlc.OutputIndex),
|
|
}
|
|
|
|
// In order to properly reconstruct the HTLC transaction, we'll need to
|
|
// re-calculate the fee required at this state, so we can add the
|
|
// correct output value amount to the transaction.
|
|
htlcFee := htlcTimeoutFee(feePewKw)
|
|
secondLevelOutputAmt := htlc.Amt - htlcFee
|
|
|
|
// With the fee calculated, re-construct the second level timeout
|
|
// transaction.
|
|
timeoutTx, err := createHtlcTimeoutTx(op, secondLevelOutputAmt,
|
|
htlc.RefundTimeout, uint32(localChanCfg.CsvDelay),
|
|
revokeKey, localKey,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// With the transaction created, we can generate a sign descriptor
|
|
// that's capable of generating the signature required to spend the
|
|
// HTLC output using the timeout transaction.
|
|
htlcCreationScript, err := senderHTLCScript(localKey, remoteKey,
|
|
revokeKey, htlc.RHash[:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
timeoutSignDesc := SignDescriptor{
|
|
PubKey: localChanCfg.PaymentBasePoint,
|
|
SingleTweak: commitTweak,
|
|
WitnessScript: htlcCreationScript,
|
|
Output: &wire.TxOut{
|
|
Value: int64(htlc.Amt),
|
|
},
|
|
HashType: txscript.SigHashAll,
|
|
SigHashes: txscript.NewTxSigHashes(timeoutTx),
|
|
InputIndex: 0,
|
|
}
|
|
|
|
// With the sign desc created, we can now construct the full witness
|
|
// for the timeout transaction, and populate it as well.
|
|
timeoutWitness, err := senderHtlcSpendTimeout(
|
|
htlc.Signature, signer, &timeoutSignDesc, timeoutTx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
timeoutTx.TxIn[0].Witness = timeoutWitness
|
|
|
|
// Finally, we'll generate the script output that the timeout
|
|
// transaction creates so we can generate the signDesc required to
|
|
// complete the claim process after a delay period.
|
|
htlcSweepScript, err := secondLevelHtlcScript(revokeKey,
|
|
delayKey, uint32(localChanCfg.CsvDelay))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &OutgoingHtlcResolution{
|
|
Expiry: htlc.RefundTimeout,
|
|
SignedTimeoutTx: timeoutTx,
|
|
SweepSignDesc: SignDescriptor{
|
|
PubKey: localChanCfg.DelayBasePoint,
|
|
SingleTweak: commitTweak,
|
|
WitnessScript: htlcSweepScript,
|
|
Output: &wire.TxOut{
|
|
Value: int64(secondLevelOutputAmt),
|
|
},
|
|
HashType: txscript.SigHashAll,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// extractHtlcResolutions creates a series of outgoing HTLC resolutions, and
|
|
// the local key used when generating the HTLC scrips. This function is to be
|
|
// used in two cases: force close, or a unilateral close.
|
|
func extractHtlcResolutions(feePerKw btcutil.Amount, ourCommit bool,
|
|
signer Signer, htlcs []*channeldb.HTLC,
|
|
commitPoint, revokeKey *btcec.PublicKey,
|
|
localChanCfg, remoteChanCfg *channeldb.ChannelConfig,
|
|
commitHash chainhash.Hash) ([]OutgoingHtlcResolution, *btcec.PublicKey, error) {
|
|
|
|
// As uusal, we start by re-generating the key-ring required to
|
|
// reconstruct the pkScripts used, and sign any transactions or inputs
|
|
// required to sweep all funds.
|
|
commitTweak := SingleTweakBytes(commitPoint,
|
|
localChanCfg.PaymentBasePoint)
|
|
localKey := TweakPubKey(localChanCfg.PaymentBasePoint, commitPoint)
|
|
delayKey := TweakPubKey(localChanCfg.DelayBasePoint, commitPoint)
|
|
remoteKey := TweakPubKey(remoteChanCfg.PaymentBasePoint, commitPoint)
|
|
|
|
dustLimit := remoteChanCfg.DustLimit
|
|
if ourCommit {
|
|
dustLimit = localChanCfg.DustLimit
|
|
}
|
|
|
|
htlcResolutions := make([]OutgoingHtlcResolution, len(htlcs))
|
|
for i, htlc := range htlcs {
|
|
// Skip any incoming HTLC's, as unless we have the pre-image to
|
|
// spend them, they'll eventually be swept by the party that
|
|
// offered the HTLC after the timeout.
|
|
if htlc.Incoming {
|
|
continue
|
|
}
|
|
|
|
// We'll also skip any HTLC's which were dust on the commitment
|
|
// transaction, as these don't have a corresponding output
|
|
// within the commitment transaction.
|
|
if htlcIsDust(htlc.Incoming, ourCommit, feePerKw, htlc.Amt,
|
|
dustLimit) {
|
|
continue
|
|
}
|
|
|
|
ohr, err := newHtlcResolution(signer, localChanCfg, commitHash,
|
|
htlc, commitTweak, delayKey, localKey, remoteKey,
|
|
revokeKey, feePerKw, dustLimit)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
// TODO(roasbeef): needs to point to proper amount including
|
|
htlcResolutions[i] = *ohr
|
|
}
|
|
|
|
return htlcResolutions, localKey, nil
|
|
}
|
|
|
|
// ForceCloseSummary describes the final commitment state before the channel is
|
|
// locked-down to initiate a force closure by broadcasting the latest state
|
|
// on-chain. The summary includes all the information required to claim all
|
|
// rightfully owned outputs.
|
|
type ForceCloseSummary struct {
|
|
// ChanPoint is the outpoint that created the channel which has been
|
|
// force closed.
|
|
ChanPoint wire.OutPoint
|
|
|
|
// SelfOutpoint is the output created by the above close tx which is
|
|
// spendable by us after a relative time delay.
|
|
SelfOutpoint wire.OutPoint
|
|
|
|
// CloseTx is the transaction which closed the channel on-chain. If we
|
|
// initiate the force close, then this'll be our latest commitment
|
|
// state. Otherwise, this'll be the state that the remote peer
|
|
// broadcasted on-chain.
|
|
CloseTx *wire.MsgTx
|
|
|
|
// SelfOutputSignDesc is a fully populated sign descriptor capable of
|
|
// generating a valid signature to sweep the self output.
|
|
SelfOutputSignDesc *SignDescriptor
|
|
|
|
// SelfOutputMaturity is the relative maturity period before the above
|
|
// output can be claimed.
|
|
SelfOutputMaturity uint32
|
|
|
|
// HtlcResolutions is a slice of HTLC resolutions which allows the
|
|
// local node to sweep any outgoing HTLC"s after the timeout period has
|
|
// passed.
|
|
HtlcResolutions []OutgoingHtlcResolution
|
|
}
|
|
|
|
// ForceClose executes a unilateral closure of the transaction at the current
|
|
// lowest commitment height of the channel. Following a force closure, all
|
|
// state transitions, or modifications to the state update logs will be
|
|
// rejected. Additionally, this function also returns a ForceCloseSummary which
|
|
// includes the necessary details required to sweep all the time-locked within
|
|
// the commitment transaction.
|
|
//
|
|
// TODO(roasbeef): all methods need to abort if in dispute state
|
|
// TODO(roasbeef): method to generate CloseSummaries for when the remote peer
|
|
// does a unilateral close
|
|
func (lc *LightningChannel) ForceClose() (*ForceCloseSummary, error) {
|
|
lc.Lock()
|
|
defer lc.Unlock()
|
|
|
|
// Set the channel state to indicate that the channel is now in a
|
|
// contested state.
|
|
lc.status = channelDispute
|
|
|
|
commitTx, err := lc.getSignedCommitTx()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Re-derive the original pkScript for to-self output within the
|
|
// commitment transaction. We'll need this to find the corresponding
|
|
// output in the commitment transaction and potentially for creating
|
|
// the sign descriptor.
|
|
csvTimeout := uint32(lc.localChanCfg.CsvDelay)
|
|
unusedRevocation, err := lc.channelState.RevocationProducer.AtIndex(
|
|
lc.currentHeight,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
commitPoint := ComputeCommitmentPoint(unusedRevocation[:])
|
|
revokeKey := DeriveRevocationPubkey(
|
|
lc.remoteChanCfg.RevocationBasePoint,
|
|
commitPoint,
|
|
)
|
|
delayKey := TweakPubKey(lc.localChanCfg.DelayBasePoint, commitPoint)
|
|
selfScript, err := commitScriptToSelf(csvTimeout, delayKey, revokeKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
payToUsScriptHash, err := witnessScriptHash(selfScript)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Locate the output index of the delayed commitment output back to us.
|
|
// We'll return the details of this output to the caller so they can
|
|
// sweep it once it's mature.
|
|
var (
|
|
delayIndex uint32
|
|
delayScript []byte
|
|
selfSignDesc *SignDescriptor
|
|
)
|
|
for i, txOut := range commitTx.TxOut {
|
|
if !bytes.Equal(payToUsScriptHash, txOut.PkScript) {
|
|
continue
|
|
}
|
|
|
|
delayIndex = uint32(i)
|
|
delayScript = txOut.PkScript
|
|
break
|
|
}
|
|
|
|
// With the necessary information gathered above, create a new sign
|
|
// descriptor which is capable of generating the signature the caller
|
|
// needs to sweep this output. The hash cache, and input index are not
|
|
// set as the caller will decide these values once sweeping the output.
|
|
// If the output is non-existent (dust), have the sign descriptor be
|
|
// nil.
|
|
if len(delayScript) != 0 {
|
|
singleTweak := SingleTweakBytes(commitPoint,
|
|
lc.localChanCfg.DelayBasePoint)
|
|
selfSignDesc = &SignDescriptor{
|
|
PubKey: lc.localChanCfg.DelayBasePoint,
|
|
SingleTweak: singleTweak,
|
|
WitnessScript: selfScript,
|
|
Output: &wire.TxOut{
|
|
PkScript: delayScript,
|
|
Value: int64(lc.channelState.LocalBalance),
|
|
},
|
|
HashType: txscript.SigHashAll,
|
|
}
|
|
}
|
|
|
|
// Once the delay output has been found (if it exists), then we'll also
|
|
// need to create a series of sign descriptors for any lingering
|
|
// outgoing HTLC's that we'll need to claim as well.
|
|
txHash := commitTx.TxHash()
|
|
htlcResolutions, _, err := extractHtlcResolutions(
|
|
lc.channelState.FeePerKw, true, lc.signer, lc.channelState.Htlcs,
|
|
commitPoint, revokeKey, lc.localChanCfg, lc.remoteChanCfg, txHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Finally, close the channel force close signal which notifies any
|
|
// subscribers that the channel has now been forcibly closed. This
|
|
// allows callers to begin to carry out any post channel closure
|
|
// activities.
|
|
close(lc.ForceCloseSignal)
|
|
|
|
return &ForceCloseSummary{
|
|
ChanPoint: lc.channelState.FundingOutpoint,
|
|
SelfOutpoint: wire.OutPoint{
|
|
Hash: commitTx.TxHash(),
|
|
Index: delayIndex,
|
|
},
|
|
CloseTx: commitTx,
|
|
SelfOutputSignDesc: selfSignDesc,
|
|
SelfOutputMaturity: csvTimeout,
|
|
HtlcResolutions: htlcResolutions,
|
|
}, nil
|
|
}
|
|
|
|
// CreateCloseProposal is used by both parties in a cooperative channel close
|
|
// workflow to generate proposed close transactions and signatures. This method
|
|
// should only be executed once all pending HTLCs (if any) on the channel have
|
|
// been cleared/removed. Upon completion, the source channel will shift into
|
|
// the "closing" state, which indicates that all incoming/outgoing HTLC
|
|
// requests should be rejected. A signature for the closing transaction is
|
|
// returned.
|
|
//
|
|
// TODO(roasbeef): caller should initiate signal to reject all incoming HTLCs,
|
|
// settle any in flight.
|
|
func (lc *LightningChannel) CreateCloseProposal(feeRate uint64,
|
|
localDeliveryScript, remoteDeliveryScript []byte) ([]byte, uint64, error) {
|
|
|
|
lc.Lock()
|
|
defer lc.Unlock()
|
|
|
|
// If we're already closing the channel, then ignore this request.
|
|
if lc.status == channelClosing || lc.status == channelClosed {
|
|
// TODO(roasbeef): check to ensure no pending payments
|
|
return nil, 0, ErrChanClosing
|
|
}
|
|
|
|
// Subtract the proposed fee from the appropriate balance, taking care
|
|
// not to persist the adjusted balance, as the feeRate may change
|
|
// during the channel closing process.
|
|
proposedFee := uint64(btcutil.Amount(feeRate) * commitWeight / 1000)
|
|
ourBalance := lc.channelState.LocalBalance
|
|
theirBalance := lc.channelState.RemoteBalance
|
|
|
|
if lc.channelState.IsInitiator {
|
|
ourBalance = ourBalance - btcutil.Amount(proposedFee)
|
|
} else {
|
|
theirBalance = theirBalance - btcutil.Amount(proposedFee)
|
|
}
|
|
|
|
closeTx := CreateCooperativeCloseTx(lc.fundingTxIn,
|
|
lc.localChanCfg.DustLimit, lc.remoteChanCfg.DustLimit,
|
|
ourBalance, theirBalance, localDeliveryScript,
|
|
remoteDeliveryScript, lc.channelState.IsInitiator)
|
|
|
|
// Ensure that the transaction doesn't explicitly violate any
|
|
// consensus rules such as being too big, or having any value with a
|
|
// negative output.
|
|
tx := btcutil.NewTx(closeTx)
|
|
if err := blockchain.CheckTransactionSanity(tx); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
// Finally, sign the completed cooperative closure transaction. As the
|
|
// initiator we'll simply send our signature over to the remote party,
|
|
// using the generated txid to be notified once the closure transaction
|
|
// has been confirmed.
|
|
lc.signDesc.SigHashes = txscript.NewTxSigHashes(closeTx)
|
|
sig, err := lc.signer.SignOutputRaw(closeTx, lc.signDesc)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
// As everything checks out, indicate in the channel status that a
|
|
// channel closure has been initiated.
|
|
lc.status = channelClosing
|
|
|
|
return sig, proposedFee, nil
|
|
}
|
|
|
|
// CompleteCooperativeClose completes the cooperative closure of the target
|
|
// active lightning channel. A fully signed closure transaction as well as the
|
|
// signature itself are returned.
|
|
//
|
|
// NOTE: The passed local and remote sigs are expected to be fully complete
|
|
// signatures including the proper sighash byte.
|
|
func (lc *LightningChannel) CompleteCooperativeClose(localSig, remoteSig,
|
|
localDeliveryScript, remoteDeliveryScript []byte,
|
|
feeRate uint64) (*wire.MsgTx, error) {
|
|
|
|
lc.Lock()
|
|
defer lc.Unlock()
|
|
|
|
// If the channel is already closed, then ignore this request.
|
|
if lc.status == channelClosed {
|
|
// TODO(roasbeef): check to ensure no pending payments
|
|
return nil, ErrChanClosing
|
|
}
|
|
|
|
// Subtract the proposed fee from the appropriate balance, taking care
|
|
// not to persist the adjusted balance, as the feeRate may change
|
|
// during the channel closing process.
|
|
proposedFee := uint64(btcutil.Amount(feeRate) * commitWeight / 1000)
|
|
ourBalance := lc.channelState.LocalBalance
|
|
theirBalance := lc.channelState.RemoteBalance
|
|
|
|
if lc.channelState.IsInitiator {
|
|
ourBalance = ourBalance - btcutil.Amount(proposedFee)
|
|
} else {
|
|
theirBalance = theirBalance - btcutil.Amount(proposedFee)
|
|
}
|
|
|
|
// Create the transaction used to return the current settled balance
|
|
// on this active channel back to both parties. In this current model,
|
|
// the initiator pays full fees for the cooperative close transaction.
|
|
closeTx := CreateCooperativeCloseTx(lc.fundingTxIn,
|
|
lc.localChanCfg.DustLimit, lc.remoteChanCfg.DustLimit,
|
|
ourBalance, theirBalance, localDeliveryScript,
|
|
remoteDeliveryScript, lc.channelState.IsInitiator)
|
|
|
|
// Ensure that the transaction doesn't explicitly validate any
|
|
// consensus rules such as being too big, or having any value with a
|
|
// negative output.
|
|
tx := btcutil.NewTx(closeTx)
|
|
if err := blockchain.CheckTransactionSanity(tx); err != nil {
|
|
return nil, err
|
|
}
|
|
hashCache := txscript.NewTxSigHashes(closeTx)
|
|
|
|
// Finally, construct the witness stack minding the order of the
|
|
// pubkeys+sigs on the stack.
|
|
ourKey := lc.localChanCfg.MultiSigKey.SerializeCompressed()
|
|
theirKey := lc.remoteChanCfg.MultiSigKey.SerializeCompressed()
|
|
witness := SpendMultiSig(lc.signDesc.WitnessScript, ourKey,
|
|
localSig, theirKey, remoteSig)
|
|
closeTx.TxIn[0].Witness = witness
|
|
|
|
// Validate the finalized transaction to ensure the output script is
|
|
// properly met, and that the remote peer supplied a valid signature.
|
|
vm, err := txscript.NewEngine(lc.fundingP2WSH, closeTx, 0,
|
|
txscript.StandardVerifyFlags, nil, hashCache,
|
|
int64(lc.channelState.Capacity))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := vm.Execute(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// As the transaction is sane, and the scripts are valid we'll mark the
|
|
// channel now as closed as the closure transaction should get into the
|
|
// chain in a timely manner and possibly be re-broadcast by the wallet.
|
|
lc.status = channelClosed
|
|
|
|
return closeTx, nil
|
|
}
|
|
|
|
// DeleteState deletes all state concerning the channel from the underlying
|
|
// database, only leaving a small summary describing metadata of the
|
|
// channel's lifetime.
|
|
func (lc *LightningChannel) DeleteState(c *channeldb.ChannelCloseSummary) error {
|
|
return lc.channelState.CloseChannel(c)
|
|
}
|
|
|
|
// StateSnapshot returns a snapshot of the current fully committed state within
|
|
// the channel.
|
|
func (lc *LightningChannel) StateSnapshot() *channeldb.ChannelSnapshot {
|
|
lc.RLock()
|
|
defer lc.RUnlock()
|
|
|
|
return lc.channelState.Snapshot()
|
|
}
|
|
|
|
// UpdateFee initiates a fee update for this channel. Must only be called by
|
|
// the channel initiator, and must be called before sending update_fee to
|
|
// the remote.
|
|
func (lc *LightningChannel) UpdateFee(feePerKw btcutil.Amount) error {
|
|
lc.Lock()
|
|
defer lc.Unlock()
|
|
|
|
// Only initiator can send fee update, so trying to send one as
|
|
// non-initiator will fail.
|
|
if !lc.channelState.IsInitiator {
|
|
return fmt.Errorf("local fee update as non-initiator")
|
|
}
|
|
|
|
lc.pendingFeeUpdate = &feePerKw
|
|
|
|
return nil
|
|
}
|
|
|
|
// ReceiveUpdateFee handles an updated fee sent from remote. This method will
|
|
// return an error if called as channel initiator.
|
|
func (lc *LightningChannel) ReceiveUpdateFee(feePerKw btcutil.Amount) error {
|
|
lc.Lock()
|
|
defer lc.Unlock()
|
|
|
|
// Only initiator can send fee update, and we must fail if we receive
|
|
// fee update as initiator
|
|
if lc.channelState.IsInitiator {
|
|
return fmt.Errorf("received fee update as initiator")
|
|
}
|
|
|
|
// TODO(halseth): should fail if fee update is unreasonable,
|
|
// as specified in BOLT#2.
|
|
lc.pendingFeeUpdate = &feePerKw
|
|
|
|
return nil
|
|
}
|
|
|
|
// CreateCommitTx creates a commitment transaction, spending from specified
|
|
// funding output. The commitment transaction contains two outputs: one paying
|
|
// to the "owner" of the commitment transaction which can be spent after a
|
|
// relative block delay or revocation event, and the other paying the
|
|
// counterparty within the channel, which can be spent immediately.
|
|
func CreateCommitTx(fundingOutput *wire.TxIn, delayKey, paymentKey *btcec.PublicKey,
|
|
revokeKey *btcec.PublicKey, csvTimeout uint32, amountToSelf,
|
|
amountToThem, dustLimit btcutil.Amount) (*wire.MsgTx, error) {
|
|
|
|
// First, we create the script for the delayed "pay-to-self" output.
|
|
// This output has 2 main redemption clauses: either we can redeem the
|
|
// output after a relative block delay, or the remote node can claim
|
|
// the funds with the revocation key if we broadcast a revoked
|
|
// commitment transaction.
|
|
ourRedeemScript, err := commitScriptToSelf(csvTimeout, delayKey,
|
|
revokeKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
payToUsScriptHash, err := witnessScriptHash(ourRedeemScript)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Next, we create the script paying to them. This is just a regular
|
|
// P2WPKH output, without any added CSV delay.
|
|
theirWitnessKeyHash, err := commitScriptUnencumbered(paymentKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Now that both output scripts have been created, we can finally create
|
|
// the transaction itself. We use a transaction version of 2 since CSV
|
|
// will fail unless the tx version is >= 2.
|
|
commitTx := wire.NewMsgTx(2)
|
|
commitTx.AddTxIn(fundingOutput)
|
|
|
|
// Avoid creating dust outputs within the commitment transaction.
|
|
if amountToSelf >= dustLimit {
|
|
commitTx.AddTxOut(&wire.TxOut{
|
|
PkScript: payToUsScriptHash,
|
|
Value: int64(amountToSelf),
|
|
})
|
|
}
|
|
if amountToThem >= dustLimit {
|
|
commitTx.AddTxOut(&wire.TxOut{
|
|
PkScript: theirWitnessKeyHash,
|
|
Value: int64(amountToThem),
|
|
})
|
|
}
|
|
|
|
return commitTx, nil
|
|
}
|
|
|
|
// CreateCooperativeCloseTx creates a transaction which if signed by both
|
|
// parties, then broadcast cooperatively closes an active channel. The creation
|
|
// of the closure transaction is modified by a boolean indicating if the party
|
|
// constructing the channel is the initiator of the closure. Currently it is
|
|
// expected that the initiator pays the transaction fees for the closing
|
|
// transaction in full.
|
|
func CreateCooperativeCloseTx(fundingTxIn *wire.TxIn,
|
|
localDust, remoteDust, ourBalance, theirBalance btcutil.Amount,
|
|
ourDeliveryScript, theirDeliveryScript []byte,
|
|
initiator bool) *wire.MsgTx {
|
|
|
|
// Construct the transaction to perform a cooperative closure of the
|
|
// channel. In the event that one side doesn't have any settled funds
|
|
// within the channel then a refund output for that particular side can
|
|
// be omitted.
|
|
closeTx := wire.NewMsgTx(2)
|
|
closeTx.AddTxIn(fundingTxIn)
|
|
|
|
// Create both cooperative closure outputs, properly respecting the
|
|
// dust limits of both parties.
|
|
if ourBalance >= localDust {
|
|
closeTx.AddTxOut(&wire.TxOut{
|
|
PkScript: ourDeliveryScript,
|
|
Value: int64(ourBalance),
|
|
})
|
|
}
|
|
if theirBalance >= remoteDust {
|
|
closeTx.AddTxOut(&wire.TxOut{
|
|
PkScript: theirDeliveryScript,
|
|
Value: int64(theirBalance),
|
|
})
|
|
}
|
|
|
|
txsort.InPlaceSort(closeTx)
|
|
|
|
return closeTx
|
|
}
|