lnd.xprv/lnwallet/channel.go

630 lines
19 KiB
Go
Raw Normal View History

package lnwallet
import (
"bytes"
"fmt"
"sync"
2016-01-16 21:45:54 +03:00
"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{}{}
fundingTxId := state.FundingTx.TxSha()
fundingPkScript, err := witnessScriptHash(state.FundingRedeemScript)
if err != nil {
return nil, err
}
_, multiSigIndex := findScriptOutputIndex(state.FundingTx, fundingPkScript)
lc.fundingTxIn = wire.NewTxIn(wire.NewOutPoint(&fundingTxId, multiSigIndex), 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.
sig, err := txscript.RawTxInSignature(c.theirPendingCommitTx, 0,
c.lnChannel.channelState.FundingRedeemScript, txscript.SigHashAll,
c.lnChannel.channelState.MultiSigKey)
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
// 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
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
}
// 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
}
// 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
}
// 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
}
// 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
}
// 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
}