45236fa092
A cooperative closure of a LightningChannel proceeds in two steps. First, the party who wishes to close the channel sends a signature for the closing transaction. Next, the responder reconstructs the closing transaction identically as the initiator did using a canonical input/output ordering, and the currently settled balance within the channel. At this point, the responder then broadcasts the closure transaction. It is the responsibility of the initiator to watch for this transaction broadcast within the network to clean up any resources they committed to the active channel.
788 lines
25 KiB
Go
788 lines
25 KiB
Go
package lnwallet
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/lightningnetwork/lnd/chainntfs"
|
|
"github.com/lightningnetwork/lnd/channeldb"
|
|
|
|
"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 (
|
|
ErrChanClosing = fmt.Errorf("channel is being closed, operation disallowed")
|
|
)
|
|
|
|
const (
|
|
// TODO(roasbeef): make not random value
|
|
MaxPendingPayments = 10
|
|
)
|
|
|
|
// channelState is an enum like type which represents the current state of a
|
|
// particular channel.
|
|
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 HTLC's 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
|
|
// HTLC's within the channel.
|
|
channelPendingPayment
|
|
)
|
|
|
|
// PaymentHash presents the hash160 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 [20]byte
|
|
|
|
// LightningChannel...
|
|
// TODO(roasbeef): future peer struct should embed this struct
|
|
type LightningChannel struct {
|
|
lnwallet *LightningWallet
|
|
channelEvents chainntnfs.ChainNotifier
|
|
|
|
sync.RWMutex
|
|
status channelState
|
|
|
|
// TODO(roasbeef): Stores all previous R values + timeouts for each
|
|
// commitment update, plus some other meta-data...Or just use OP_RETURN
|
|
// to help out?
|
|
// currently going for: nSequence/nLockTime overloading
|
|
channelDB *channeldb.DB
|
|
|
|
// stateMtx protects concurrent access to the state struct.
|
|
stateMtx sync.RWMutex
|
|
channelState *channeldb.OpenChannel
|
|
|
|
updateTotem chan struct{}
|
|
|
|
// Uncleared HTLC's.
|
|
pendingPayments map[PaymentHash]*PaymentDescriptor
|
|
|
|
// Payment's which we've requested.
|
|
unfufilledPayments map[PaymentHash]*PaymentRequest
|
|
|
|
fundingTxIn *wire.TxIn
|
|
fundingP2SH []byte
|
|
|
|
// TODO(roasbeef): create and embed 'Service' interface w/ below?
|
|
started int32
|
|
shutdown int32
|
|
|
|
quit chan struct{}
|
|
wg sync.WaitGroup
|
|
}
|
|
|
|
// newLightningChannel...
|
|
func NewLightningChannel(wallet *LightningWallet, events chainntnfs.ChainNotifier,
|
|
chanDB *channeldb.DB, state *channeldb.OpenChannel) (*LightningChannel, error) {
|
|
|
|
lc := &LightningChannel{
|
|
lnwallet: wallet,
|
|
channelEvents: events,
|
|
channelState: state,
|
|
channelDB: chanDB,
|
|
updateTotem: make(chan struct{}, 1),
|
|
pendingPayments: make(map[PaymentHash]*PaymentDescriptor),
|
|
unfufilledPayments: make(map[PaymentHash]*PaymentRequest),
|
|
}
|
|
|
|
// TODO(roasbeef): do a NotifySpent for the funding input, and
|
|
// NotifyReceived for all commitment outputs.
|
|
|
|
// Populate the totem.
|
|
lc.updateTotem <- struct{}{}
|
|
|
|
fundingPkScript, err := witnessScriptHash(state.FundingRedeemScript)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
lc.fundingTxIn = wire.NewTxIn(state.FundingOutpoint, nil, nil)
|
|
lc.fundingP2SH = fundingPkScript
|
|
|
|
return lc, nil
|
|
}
|
|
|
|
// PaymentDescriptor...
|
|
type PaymentDescriptor struct {
|
|
RHash [20]byte
|
|
Timeout uint32
|
|
Value btcutil.Amount
|
|
|
|
OurRevocation [20]byte // TODO(roasbeef): don't need these?
|
|
TheirRevocation [20]byte
|
|
|
|
PayToUs bool
|
|
}
|
|
|
|
// ChannelUpdate...
|
|
type ChannelUpdate struct {
|
|
pendingDesc *PaymentDescriptor
|
|
deletion bool
|
|
|
|
currentUpdateNum uint64
|
|
pendingUpdateNum uint64
|
|
|
|
ourPendingCommitTx *wire.MsgTx
|
|
theirPendingCommitTx *wire.MsgTx
|
|
|
|
pendingRevocation [20]byte
|
|
sigTheirNewCommit []byte
|
|
|
|
// TODO(roasbeef): some enum to track current state in lifetime?
|
|
// state UpdateStag
|
|
|
|
lnChannel *LightningChannel
|
|
}
|
|
|
|
// RevocationHash...
|
|
func (c *ChannelUpdate) RevocationHash() ([]byte, error) {
|
|
c.lnChannel.stateMtx.RLock()
|
|
defer c.lnChannel.stateMtx.RUnlock()
|
|
|
|
e := c.lnChannel.channelState.LocalElkrem
|
|
nextPreimage, err := e.AtIndex(c.pendingUpdateNum)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return btcutil.Hash160(nextPreimage[:]), nil
|
|
}
|
|
|
|
// SignCounterPartyCommitment...
|
|
func (c *ChannelUpdate) SignCounterPartyCommitment() ([]byte, error) {
|
|
c.lnChannel.stateMtx.RLock()
|
|
defer c.lnChannel.stateMtx.RUnlock()
|
|
|
|
if c.sigTheirNewCommit != nil {
|
|
return c.sigTheirNewCommit, nil
|
|
}
|
|
|
|
// Sign their version of the commitment transaction.
|
|
hashCache := txscript.NewTxSigHashes(c.theirPendingCommitTx)
|
|
sig, err := txscript.RawTxInWitnessSignature(c.theirPendingCommitTx,
|
|
hashCache, 0, int64(c.lnChannel.channelState.Capacity),
|
|
c.lnChannel.channelState.FundingRedeemScript, txscript.SigHashAll,
|
|
c.lnChannel.channelState.OurMultiSigKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
c.sigTheirNewCommit = sig
|
|
|
|
return sig, nil
|
|
}
|
|
|
|
// PreviousRevocationPreImage...
|
|
func (c *ChannelUpdate) PreviousRevocationPreImage() ([]byte, error) {
|
|
c.lnChannel.stateMtx.RLock()
|
|
defer c.lnChannel.stateMtx.RUnlock()
|
|
|
|
// Retrieve the pre-image to the revocation hash our current commitment
|
|
// transaction.
|
|
e := c.lnChannel.channelState.LocalElkrem
|
|
revokePreImage, err := e.AtIndex(c.currentUpdateNum)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return revokePreImage[:], nil
|
|
}
|
|
|
|
// VerifyNewCommitmentSigs...
|
|
func (c *ChannelUpdate) VerifyNewCommitmentSigs(ourSig, theirSig []byte) error {
|
|
c.lnChannel.stateMtx.RLock()
|
|
defer c.lnChannel.stateMtx.RUnlock()
|
|
|
|
channelState := c.lnChannel.channelState
|
|
|
|
// TODO(roasbeef): replace with sighash calc and regular sig check
|
|
// after merge
|
|
|
|
// When initially generating the redeemScript, we sorted the serialized
|
|
// public keys in descending order. So we do a quick comparison in order
|
|
// ensure the signatures appear on the Script Virual Machine stack in
|
|
// the correct order.
|
|
redeemScript := channelState.FundingRedeemScript
|
|
ourKey := channelState.OurCommitKey.PubKey().SerializeCompressed()
|
|
theirKey := channelState.TheirCommitKey.SerializeCompressed()
|
|
witness := spendMultiSig(redeemScript, ourKey, ourSig, theirKey, theirSig)
|
|
|
|
// Attach the scriptSig to our commitment transaction's only input,
|
|
// then validate that the scriptSig executes correctly.
|
|
commitTx := c.ourPendingCommitTx
|
|
commitTx.TxIn[0].Witness = witness
|
|
// TODO(roasbeef): need hashcache and value here
|
|
vm, err := txscript.NewEngine(c.lnChannel.fundingP2SH, commitTx, 0,
|
|
txscript.StandardVerifyFlags, nil, nil, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return vm.Execute()
|
|
}
|
|
|
|
// Commit...
|
|
func (c *ChannelUpdate) Commit(pastRevokePreimage []byte) error {
|
|
c.lnChannel.stateMtx.Lock()
|
|
defer c.lnChannel.stateMtx.Unlock()
|
|
|
|
// First, ensure that the pre-image properly links into the shachain.
|
|
//theirShaChain := c.lnChannel.channelState.TheirShaChain
|
|
//var preImage [32]byte
|
|
//copy(preImage[:], pastRevokePreimage)
|
|
//if err := theirShaChain.AddNextHash(preImage); err != nil {
|
|
// return err
|
|
//}
|
|
|
|
channelState := c.lnChannel.channelState
|
|
|
|
// Finally, verify that that this is indeed the pre-image to the
|
|
// revocation hash we were given earlier.
|
|
if !bytes.Equal(btcutil.Hash160(pastRevokePreimage),
|
|
channelState.TheirCurrentRevocation[:]) {
|
|
return fmt.Errorf("pre-image hash does not match revocation")
|
|
}
|
|
|
|
// Store this current revocation in the channel state so we can
|
|
// verify future channel updates.
|
|
channelState.TheirCurrentRevocation = c.pendingRevocation
|
|
|
|
// The channel update is now complete, roll over to the newest commitment
|
|
// transaction.
|
|
channelState.OurCommitTx = c.ourPendingCommitTx
|
|
channelState.TheirCommitTx = c.theirPendingCommitTx
|
|
channelState.NumUpdates = c.pendingUpdateNum
|
|
|
|
// If this channel update involved deleting an HTLC, remove it from the
|
|
// set of pending payments.
|
|
if c.deletion {
|
|
delete(c.lnChannel.pendingPayments, c.pendingDesc.RHash)
|
|
}
|
|
|
|
// TODO(roasbeef): db writes, checkpoints, and such
|
|
|
|
// Return the updateTotem, allowing another update to be created now
|
|
// that this pending update has been commited, and finalized.
|
|
c.lnChannel.updateTotem <- struct{}{}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ChannelPoint returns the outpoint of the original funding transaction which
|
|
// created this active channel. This outpoint is used throughout various
|
|
// sub-systems to uniquely identify an open channel.
|
|
func (lc *LightningChannel) ChannelPoint() wire.OutPoint {
|
|
return lc.fundingTxIn.PreviousOutPoint
|
|
}
|
|
|
|
// AddHTLC...
|
|
// 1. request R_Hash from receiver (only if single hop, would be out of band)
|
|
// 2. propose HTLC
|
|
// * timeout
|
|
// * value
|
|
// * r_hash
|
|
// * next revocation hash
|
|
// 3. they accept
|
|
// * their next revocation hash
|
|
// * their sig for our new commitment tx (verify correctness)
|
|
// Can buld both new commitment txns at this point
|
|
// 4. we give sigs
|
|
// * our sigs for their new commitment tx
|
|
// * the pre-image to our old commitment tx
|
|
// 5. they complete
|
|
// * the pre-image to their old commitment tx (verify is part of their chain, is pre-image)
|
|
func (lc *LightningChannel) AddHTLC(timeout uint32, value btcutil.Amount,
|
|
rHash, revocation PaymentHash, payToUs bool) (*ChannelUpdate, error) {
|
|
|
|
// Grab the updateTotem, this acts as a barrier upholding the invariant
|
|
// that only one channel update transaction should exist at any moment.
|
|
// This aides in ensuring the channel updates are atomic, and consistent.
|
|
<-lc.updateTotem
|
|
|
|
chanUpdate := &ChannelUpdate{
|
|
pendingDesc: &PaymentDescriptor{
|
|
RHash: rHash,
|
|
TheirRevocation: revocation,
|
|
Timeout: timeout,
|
|
Value: value,
|
|
PayToUs: payToUs,
|
|
},
|
|
pendingRevocation: revocation,
|
|
lnChannel: lc,
|
|
}
|
|
|
|
// Get next revocation hash, updating the number of updates in the
|
|
// channel as a result.
|
|
chanUpdate.currentUpdateNum = lc.channelState.NumUpdates
|
|
chanUpdate.pendingUpdateNum = lc.channelState.NumUpdates + 1
|
|
nextPreimage, err := lc.channelState.LocalElkrem.AtIndex(chanUpdate.pendingUpdateNum)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
copy(chanUpdate.pendingDesc.OurRevocation[:], btcutil.Hash160(nextPreimage[:]))
|
|
|
|
// Re-calculate the amount of cleared funds for each side.
|
|
var amountToUs, amountToThem btcutil.Amount
|
|
if payToUs {
|
|
amountToUs = lc.channelState.OurBalance
|
|
amountToThem = lc.channelState.TheirBalance - value
|
|
} else {
|
|
amountToUs = lc.channelState.OurBalance - value
|
|
amountToThem = lc.channelState.TheirBalance
|
|
}
|
|
|
|
// Re-create copies of the current commitment transactions to be updated.
|
|
ourNewCommitTx, theirNewCommitTx, err := createNewCommitmentTxns(
|
|
lc.fundingTxIn, lc.channelState, chanUpdate, amountToUs, amountToThem,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// First, re-add all the old HTLCs.
|
|
for _, paymentDesc := range lc.pendingPayments {
|
|
if err := lc.addHTLC(ourNewCommitTx, theirNewCommitTx, paymentDesc); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Then add this new HTLC.
|
|
if err := lc.addHTLC(ourNewCommitTx, theirNewCommitTx, chanUpdate.pendingDesc); err != nil {
|
|
return nil, err
|
|
}
|
|
lc.pendingPayments[rHash] = chanUpdate.pendingDesc // TODO(roasbeef): check for dups?
|
|
|
|
// Sort both transactions according to the agreed upon cannonical
|
|
// ordering. This lets us skip sending the entire transaction over,
|
|
// instead we'll just send signatures.
|
|
txsort.InPlaceSort(ourNewCommitTx)
|
|
txsort.InPlaceSort(theirNewCommitTx)
|
|
|
|
// TODO(roasbeef): locktimes/sequence set
|
|
|
|
// TODO(roasbeef): write checkpoint here...
|
|
|
|
chanUpdate.ourPendingCommitTx = ourNewCommitTx
|
|
chanUpdate.theirPendingCommitTx = theirNewCommitTx
|
|
|
|
return chanUpdate, nil
|
|
}
|
|
|
|
// addHTLC...
|
|
// NOTE: This MUST be called with stateMtx held.
|
|
func (lc *LightningChannel) addHTLC(ourCommitTx, theirCommitTx *wire.MsgTx,
|
|
paymentDesc *PaymentDescriptor) error {
|
|
|
|
// If the HTLC is going to us, then we're the sender, otherwise they
|
|
// are.
|
|
var senderKey, receiverKey *btcec.PublicKey
|
|
var senderRevocation, receiverRevocation []byte
|
|
if paymentDesc.PayToUs {
|
|
receiverKey = lc.channelState.OurCommitKey.PubKey()
|
|
receiverRevocation = paymentDesc.OurRevocation[:]
|
|
senderKey = lc.channelState.TheirCommitKey
|
|
senderRevocation = paymentDesc.TheirRevocation[:]
|
|
} else {
|
|
senderKey = lc.channelState.OurCommitKey.PubKey()
|
|
senderRevocation = paymentDesc.OurRevocation[:]
|
|
receiverKey = lc.channelState.TheirCommitKey
|
|
receiverRevocation = paymentDesc.TheirRevocation[:]
|
|
}
|
|
|
|
// Generate the proper redeem scripts for the HTLC output for both the
|
|
// sender and the receiver.
|
|
timeout := paymentDesc.Timeout
|
|
rHash := paymentDesc.RHash
|
|
delay := lc.channelState.LocalCsvDelay
|
|
senderPKScript, err := senderHTLCScript(timeout, delay, senderKey,
|
|
receiverKey, senderRevocation[:], rHash[:])
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
receiverPKScript, err := receiverHTLCScript(timeout, delay, senderKey,
|
|
receiverKey, receiverRevocation[:], rHash[:])
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
// Now that we have the redeem scripts, create the P2WSH public key
|
|
// script for each.
|
|
senderP2SH, err := witnessScriptHash(senderPKScript)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
receiverP2SH, err := witnessScriptHash(receiverPKScript)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
// Add the new HTLC outputs to the respective commitment transactions.
|
|
amountPending := int64(paymentDesc.Value)
|
|
if paymentDesc.PayToUs {
|
|
ourCommitTx.AddTxOut(wire.NewTxOut(amountPending, receiverP2SH))
|
|
theirCommitTx.AddTxOut(wire.NewTxOut(amountPending, senderP2SH))
|
|
} else {
|
|
ourCommitTx.AddTxOut(wire.NewTxOut(amountPending, senderP2SH))
|
|
theirCommitTx.AddTxOut(wire.NewTxOut(amountPending, receiverP2SH))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SettleHTLC...
|
|
// R-VALUE, NEW REVOKE HASH
|
|
// accept, sig
|
|
func (lc *LightningChannel) SettleHTLC(rValue [20]byte, newRevocation [20]byte) (*ChannelUpdate, error) {
|
|
// Grab the updateTotem, this acts as a barrier upholding the invariant
|
|
// that only one channel update transaction should exist at any moment.
|
|
// This aides in ensuring the channel updates are atomic, and consistent.
|
|
<-lc.updateTotem
|
|
|
|
// Find the matching payment descriptor, bailing out early if it
|
|
// doesn't exist.
|
|
var rHash PaymentHash
|
|
copy(rHash[:], btcutil.Hash160(rValue[:]))
|
|
payDesc, ok := lc.pendingPayments[rHash]
|
|
if !ok {
|
|
return nil, fmt.Errorf("r-hash for preimage not found")
|
|
}
|
|
|
|
chanUpdate := &ChannelUpdate{
|
|
pendingDesc: payDesc,
|
|
deletion: true,
|
|
pendingRevocation: newRevocation,
|
|
lnChannel: lc,
|
|
}
|
|
|
|
// TODO(roasbeef): such copy pasta, make into func...
|
|
// Get next revocation hash, updating the number of updates in the
|
|
// channel as a result.
|
|
chanUpdate.currentUpdateNum = lc.channelState.NumUpdates
|
|
chanUpdate.pendingUpdateNum = lc.channelState.NumUpdates + 1
|
|
nextPreimage, err := lc.channelState.LocalElkrem.AtIndex(chanUpdate.pendingUpdateNum)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
copy(chanUpdate.pendingDesc.OurRevocation[:], btcutil.Hash160(nextPreimage[:]))
|
|
|
|
// Re-calculate the amount of cleared funds for each side.
|
|
var amountToUs, amountToThem btcutil.Amount
|
|
if payDesc.PayToUs {
|
|
amountToUs = lc.channelState.OurBalance + payDesc.Value
|
|
amountToThem = lc.channelState.TheirBalance
|
|
} else {
|
|
amountToUs = lc.channelState.OurBalance
|
|
amountToThem = lc.channelState.TheirBalance + payDesc.Value
|
|
}
|
|
|
|
// Create new commitment transactions that reflect the settlement of
|
|
// this pending HTLC.
|
|
ourNewCommitTx, theirNewCommitTx, err := createNewCommitmentTxns(
|
|
lc.fundingTxIn, lc.channelState, chanUpdate, amountToUs, amountToThem,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Re-add all the HTLC's skipping over this newly settled payment.
|
|
for paymentHash, paymentDesc := range lc.pendingPayments {
|
|
if bytes.Equal(paymentHash[:], rHash[:]) {
|
|
continue
|
|
}
|
|
if err := lc.addHTLC(ourNewCommitTx, theirNewCommitTx, paymentDesc); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Sort both transactions according to the agreed upon cannonical
|
|
// ordering. This lets us skip sending the entire transaction over,
|
|
// instead we'll just send signatures.
|
|
txsort.InPlaceSort(ourNewCommitTx)
|
|
txsort.InPlaceSort(theirNewCommitTx)
|
|
|
|
// TODO(roasbeef): locktimes/sequence set
|
|
|
|
// TODO(roasbeef): write checkpoint here...
|
|
|
|
chanUpdate.ourPendingCommitTx = ourNewCommitTx
|
|
chanUpdate.theirPendingCommitTx = theirNewCommitTx
|
|
|
|
return chanUpdate, nil
|
|
}
|
|
|
|
// CancelHTLC...
|
|
func (lc *LightningChannel) CancelHTLC() error {
|
|
return nil
|
|
}
|
|
|
|
// OurBalance...
|
|
func (lc *LightningChannel) OurBalance() btcutil.Amount {
|
|
lc.stateMtx.RLock()
|
|
defer lc.stateMtx.RUnlock()
|
|
return lc.channelState.OurBalance
|
|
}
|
|
|
|
// TheirBalance...
|
|
func (lc *LightningChannel) TheirBalance() btcutil.Amount {
|
|
lc.stateMtx.RLock()
|
|
defer lc.stateMtx.RUnlock()
|
|
return lc.channelState.TheirBalance
|
|
}
|
|
|
|
// ForceClose...
|
|
func (lc *LightningChannel) ForceClose() error {
|
|
return nil
|
|
}
|
|
|
|
// InitCooperativeClose initiates a cooperative closure of an active lightning
|
|
// channel. 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,
|
|
// and the txid of the closing transaction are returned. The initiator of the
|
|
// channel closure should then watch the blockchain for a confirmation of the
|
|
// closing transaction before considering the channel terminated. In the case
|
|
// of an unresponsive remote party, the initiator can either choose to execute
|
|
// a force closure, or backoff for a period of time, and retry the cooperative
|
|
// closure.
|
|
// TODO(roasbeef): caller should initiate signal to reject all incoming HTLCs,
|
|
// settle any inflight.
|
|
func (lc *LightningChannel) InitCooperativeClose() ([]byte, *wire.ShaHash, error) {
|
|
lc.Lock()
|
|
defer lc.Unlock() // TODO(roasbeef): coarser graiend locking
|
|
|
|
// 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, nil, ErrChanClosing
|
|
}
|
|
|
|
// Otherwise, indicate in the channel status that a channel closure has
|
|
// been initiated.
|
|
lc.status = channelClosing
|
|
|
|
// TODO(roasbeef): assumes initiator pays fees
|
|
closeTx := createCooperativeCloseTx(lc.fundingTxIn,
|
|
lc.channelState.OurBalance, lc.channelState.TheirBalance,
|
|
lc.channelState.OurDeliveryScript, lc.channelState.TheirDeliveryScript,
|
|
true)
|
|
closeTxSha := closeTx.TxSha()
|
|
|
|
// Finally, sign the completed cooperative closure transaction. As the
|
|
// initiator we'll simply send our signature over the the remote party,
|
|
// using the generated txid to be notified once the closure transaction
|
|
// has been confirmed.
|
|
hashCache := txscript.NewTxSigHashes(closeTx)
|
|
closeSig, err := txscript.RawTxInWitnessSignature(closeTx,
|
|
hashCache, 0, int64(lc.channelState.Capacity),
|
|
lc.channelState.FundingRedeemScript, txscript.SigHashAll,
|
|
lc.channelState.OurMultiSigKey)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return closeSig, &closeTxSha, nil
|
|
}
|
|
|
|
// CompleteCooperativeClose completes the cooperative closure of the target
|
|
// active lightning channel. This method should be called in response to the
|
|
// remote node initating a cooperative channel closure. A fully signed closure
|
|
// transaction is returned. It is the duty of the responding node to broadcast
|
|
// a signed+valid closure transaction to the network.
|
|
func (lc *LightningChannel) CompleteCooperativeClose(remoteSig []byte) (*wire.MsgTx, error) {
|
|
lc.Lock()
|
|
defer lc.Unlock() // TODO(roasbeef): coarser graiend locking
|
|
|
|
// 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, ErrChanClosing
|
|
}
|
|
|
|
lc.status = channelClosed
|
|
|
|
// 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.channelState.OurBalance, lc.channelState.TheirBalance,
|
|
lc.channelState.OurDeliveryScript, lc.channelState.TheirDeliveryScript,
|
|
false)
|
|
|
|
// With the transaction created, we can finally generate our half of
|
|
// the 2-of-2 multi-sig needed to redeem the funding output.
|
|
redeemScript := lc.channelState.FundingRedeemScript
|
|
hashCache := txscript.NewTxSigHashes(closeTx)
|
|
closeSig, err := txscript.RawTxInWitnessSignature(closeTx,
|
|
hashCache, 0, int64(lc.channelState.Capacity),
|
|
redeemScript, txscript.SigHashAll,
|
|
lc.channelState.OurMultiSigKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Finally, construct the witness stack minding the order of the
|
|
// pubkeys+sigs on the stack.
|
|
ourKey := lc.channelState.OurMultiSigKey.PubKey().SerializeCompressed()
|
|
theirKey := lc.channelState.TheirMultiSigKey.SerializeCompressed()
|
|
witness := spendMultiSig(redeemScript, ourKey, closeSig,
|
|
theirKey, remoteSig)
|
|
closeTx.TxIn[0].Witness = witness
|
|
|
|
// TODO(roasbeef): VALIDATE
|
|
|
|
return closeTx, nil
|
|
}
|
|
|
|
// RequestPayment...
|
|
func (lc *LightningChannel) RequestPayment(amount btcutil.Amount) error {
|
|
// Validate amount
|
|
return nil
|
|
}
|
|
|
|
// PaymentRequest...
|
|
// TODO(roasbeef): serialization (bip 70, QR code, etc)
|
|
// * routing handled by upper layer
|
|
type PaymentRequest struct {
|
|
PaymentPreImage [20]byte
|
|
Value btcutil.Amount
|
|
}
|
|
|
|
// createNewCommitmentTxns....
|
|
// NOTE: This MUST be called with stateMtx held.
|
|
func createNewCommitmentTxns(fundingTxIn *wire.TxIn, state *channeldb.OpenChannel,
|
|
chanUpdate *ChannelUpdate, amountToUs, amountToThem btcutil.Amount) (*wire.MsgTx, *wire.MsgTx, error) {
|
|
|
|
ourNewCommitTx, err := createCommitTx(fundingTxIn,
|
|
state.OurCommitKey.PubKey(), state.TheirCommitKey,
|
|
chanUpdate.pendingDesc.OurRevocation[:], state.LocalCsvDelay,
|
|
amountToUs, amountToThem)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
theirNewCommitTx, err := createCommitTx(fundingTxIn,
|
|
state.TheirCommitKey, state.OurCommitKey.PubKey(),
|
|
chanUpdate.pendingDesc.TheirRevocation[:], state.RemoteCsvDelay,
|
|
amountToThem, amountToUs)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return ourNewCommitTx, theirNewCommitTx, nil
|
|
}
|
|
|
|
// createCommitTx...
|
|
// TODO(roasbeef): fix inconsistency of 32 vs 20 byte revocation hashes everywhere...
|
|
func createCommitTx(fundingOutput *wire.TxIn, selfKey, theirKey *btcec.PublicKey,
|
|
revokeHash []byte, csvTimeout uint32, amountToSelf,
|
|
amountToThem btcutil.Amount) (*wire.MsgTx, error) {
|
|
|
|
// First, we create the script for the delayed "pay-to-self" output.
|
|
ourRedeemScript, err := commitScriptToSelf(csvTimeout, selfKey, theirKey,
|
|
revokeHash)
|
|
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
|
|
// P2PKH-like output, without any added CSV delay. However, we instead
|
|
// use P2SH.
|
|
theirRedeemScript, err := commitScriptUnencumbered(theirKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
payToThemScriptHash, err := witnessScriptHash(theirRedeemScript)
|
|
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()
|
|
commitTx.Version = 2
|
|
commitTx.AddTxIn(fundingOutput)
|
|
commitTx.AddTxOut(wire.NewTxOut(int64(amountToSelf), payToUsScriptHash))
|
|
commitTx.AddTxOut(wire.NewTxOut(int64(amountToThem), payToThemScriptHash))
|
|
|
|
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,
|
|
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()
|
|
closeTx.AddTxIn(fundingTxIn)
|
|
|
|
// The initiator the a cooperative closure pays the fee in entirety.
|
|
// Determine if we're the initiator so we can compute fees properly.
|
|
if initiator {
|
|
// TODO(roasbeef): take sat/byte here instead of properly calc
|
|
ourBalance -= 5000
|
|
} else {
|
|
theirBalance -= 5000
|
|
}
|
|
|
|
// TODO(roasbeef): dust check...
|
|
// * although upper layers should prevent
|
|
if ourBalance != 0 {
|
|
closeTx.AddTxOut(&wire.TxOut{
|
|
PkScript: ourDeliveryScript,
|
|
Value: int64(ourBalance),
|
|
})
|
|
}
|
|
if theirBalance != 0 {
|
|
closeTx.AddTxOut(&wire.TxOut{
|
|
PkScript: theirDeliveryScript,
|
|
Value: int64(theirBalance),
|
|
})
|
|
}
|
|
|
|
txsort.InPlaceSort(closeTx)
|
|
|
|
return closeTx
|
|
}
|