diff --git a/channeldb/channel.go b/channeldb/channel.go index 0d7e6cd6..784478b7 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -282,6 +282,8 @@ type HTLC struct { // sweep the funds on-chain in the case of a unilateral channel // closure. RevocationDelay uint32 + + // TODO(roasbeef): add output index? } // Copy returns a full copy of the target HTLC. diff --git a/lnd.go b/lnd.go index 74b801cf..9ea8f0c7 100644 --- a/lnd.go +++ b/lnd.go @@ -154,7 +154,7 @@ func lndMain() error { defaultListenAddrs := []string{ net.JoinHostPort("", strconv.Itoa(loadedConfig.PeerPort)), } - server, err := newServer(defaultListenAddrs, notifier, wallet, chanDB) + server, err := newServer(defaultListenAddrs, notifier, bio, wallet, chanDB) if err != nil { srvrLog.Errorf("unable to create server: %v\n", err) return err diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 524b9a37..87344091 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -310,14 +310,11 @@ func (s *commitmentChain) tail() *commitment { // // See the individual comments within the above methods for further details. type LightningChannel struct { - // TODO(roasbeef): temporarily replace wallet with either goroutine or - // BlockChainIO interface? Later once all signing is done through - // wallet can add back. - lnwallet *LightningWallet - signer Signer signDesc *SignDescriptor + bio BlockChainIO + channelEvents chainntnfs.ChainNotifier sync.RWMutex @@ -389,6 +386,16 @@ type LightningChannel struct { 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{} + started int32 shutdown int32 @@ -397,38 +404,40 @@ type LightningChannel struct { } // NewLightningChannel creates a new, active payment channel given an -// implementation of the wallet controller, 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, wallet *LightningWallet, +// 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, bio BlockChainIO, events chainntnfs.ChainNotifier, state *channeldb.OpenChannel) (*LightningChannel, error) { // TODO(roasbeef): remove events+wallet lc := &LightningChannel{ - signer: signer, - lnwallet: wallet, - channelEvents: events, - currentHeight: state.NumUpdates, - remoteCommitChain: newCommitmentChain(state.NumUpdates), - localCommitChain: newCommitmentChain(state.NumUpdates), - channelState: state, - revocationWindowEdge: state.NumUpdates, - ourUpdateLog: list.New(), - theirUpdateLog: list.New(), - ourLogIndex: make(map[uint32]*list.Element), - theirLogIndex: make(map[uint32]*list.Element), - Capacity: state.Capacity, - LocalDeliveryScript: state.OurDeliveryScript, - RemoteDeliveryScript: state.TheirDeliveryScript, - FundingRedeemScript: state.FundingRedeemScript, + signer: signer, + bio: bio, + channelEvents: events, + currentHeight: state.NumUpdates, + remoteCommitChain: newCommitmentChain(state.NumUpdates), + localCommitChain: newCommitmentChain(state.NumUpdates), + channelState: state, + revocationWindowEdge: state.NumUpdates, + ourUpdateLog: list.New(), + theirUpdateLog: list.New(), + ourLogIndex: make(map[uint32]*list.Element), + theirLogIndex: make(map[uint32]*list.Element), + Capacity: state.Capacity, + LocalDeliveryScript: state.OurDeliveryScript, + RemoteDeliveryScript: state.TheirDeliveryScript, + FundingRedeemScript: state.FundingRedeemScript, + ForceCloseSignal: make(chan struct{}), + UnilateralCloseSignal: make(chan struct{}), } // Initialize both of our chains the current un-revoked commitment for // each side. // TODO(roasbeef): add chnneldb.RevocationLogTail method, then init - // their commitment from that + // their commitment from that as we may be de-synced initialCommitment := &commitment{ height: lc.currentHeight, ourBalance: state.OurBalance, @@ -445,16 +454,15 @@ func NewLightningChannel(signer Signer, wallet *LightningWallet, lc.restoreStateLogs() } - // TODO(roasbeef): do a NotifySpent for the funding input, and - // NotifyReceived for all commitment outputs. - + // 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(state.FundingRedeemScript) if err != nil { return nil, err } lc.fundingTxIn = wire.NewTxIn(state.FundingOutpoint, nil, nil) lc.fundingP2WSH = fundingPkScript - lc.signDesc = &SignDescriptor{ PubKey: lc.channelState.OurMultiSigKey, RedeemScript: lc.channelState.FundingRedeemScript, @@ -466,6 +474,38 @@ func NewLightningChannel(signer Signer, wallet *LightningWallet, InputIndex: 0, } + // 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 + channelCloseNtfn, err := lc.channelEvents.RegisterSpendNtfn(fundingOut) + if err != nil { + return nil, err + } + + // TODO(roasbeef) move into the peer's htlcManager? + // * if not, send the SpendDetail over the channel instead of just + // closing it + go func() { + // If the daemon is shutting down, then this notification channel + // will be closed, so check the second read-value to avoid a false + // positive. + if _, ok := <-channelCloseNtfn.Spend; !ok { + return + } + + // If the channel's doesn't already indicate that a commitment + // transaction has been broadcast on-chain, then this means the + // remote party broadcasted their commitment transaction. + // TODO(roasbeef): wait for a conf? + lc.Lock() + if lc.status != channelDispute { + close(lc.UnilateralCloseSignal) + lc.status = channelDispute + } + lc.Unlock() + }() + return lc, nil } @@ -1379,9 +1419,135 @@ func (lc *LightningChannel) addHTLC(commitTx *wire.MsgTx, ourCommit bool, return nil } -// ForceClose... -func (lc *LightningChannel) ForceClose() error { - return 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. +// TODO(roasbeef): generalize, add HTLC info, revocatio info, etc. +type ForceCloseSummary struct { + // 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 + + // SelfOutpoint is the output created by the above close tx which is + // spendable by us after a relative time delay. + SelfOutpoint wire.OutPoint + + // SelfOutputMaturity is the relative maturity period before the above + // output can be claimed. + SelfOutputMaturity uint32 + + // SelfOutputSignDesc is a fully populated sign descriptor capable of + // generating a valid signature to swee the self output. + SelfOutputSignDesc *SignDescriptor +} + +// 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 + + // Fetch the current commitment transaction, along with their signature + // for the transaction. + commitTx := lc.channelState.OurCommitTx + theirSig := append(lc.channelState.OurCommitSig, 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.channelState.OurMultiSigKey.SerializeCompressed() + theirKey := lc.channelState.TheirMultiSigKey.SerializeCompressed() + witness := SpendMultiSig(lc.FundingRedeemScript, ourKey, ourSig, + theirKey, theirSig) + commitTx.TxIn[0].Witness = witness + + // 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. + // TODO(roasbeef): also return HTLC info, assumes only p2wsh is commit + // tx + var delayIndex uint32 + var delayScript []byte + for i, txOut := range commitTx.TxOut { + if !txscript.IsPayToWitnessScriptHash(txOut.PkScript) { + continue + } + + delayIndex = uint32(i) + delayScript = txOut.PkScript + } + + csvTimeout := lc.channelState.LocalCsvDelay + selfKey := lc.channelState.OurCommitKey + + // Re-derive the original pkScript for out to-self output within the + // commitment transaction. We'll need this for the created sign + // descriptor. + elkrem := lc.channelState.LocalElkrem + unusedRevocation, err := elkrem.AtIndex(lc.currentHeight) + if err != nil { + return nil, err + } + revokeKey := DeriveRevocationPubkey(lc.channelState.TheirCommitKey, + unusedRevocation[:]) + selfScript, err := commitScriptToSelf(csvTimeout, selfKey, revokeKey) + if err != nil { + return nil, err + } + + // With the necessary information gatehred 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. + selfSignDesc := &SignDescriptor{ + PubKey: selfKey, + RedeemScript: selfScript, + Output: &wire.TxOut{ + PkScript: delayScript, + Value: int64(lc.channelState.OurBalance), + }, + HashType: txscript.SigHashAll, + } + + // 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{ + CloseTx: commitTx, + SelfOutpoint: wire.OutPoint{ + Hash: commitTx.TxSha(), + Index: delayIndex, + }, + SelfOutputMaturity: csvTimeout, + SelfOutputSignDesc: selfSignDesc, + }, nil } // InitCooperativeClose initiates a cooperative closure of an active lightning diff --git a/lnwallet/interface.go b/lnwallet/interface.go index a29dffcc..3e563dcc 100644 --- a/lnwallet/interface.go +++ b/lnwallet/interface.go @@ -130,6 +130,7 @@ type WalletController interface { // to date data possible. // // TODO(roasbeef): move to diff package perhaps? +// TODO(roasbeef): move publish txn here? type BlockChainIO interface { // GetCurrentHeight returns the current height of the valid most-work // chain the implementation is aware of. diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index 41c1894e..529f4dac 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -1142,7 +1142,7 @@ func (l *LightningWallet) handleChannelOpen(req *channelOpenMsg) { // Finally, create and officially open the payment channel! // TODO(roasbeef): CreationTime once tx is 'open' - channel, _ := NewLightningChannel(l.Signer, l, l.chainNotifier, res.partialState) + channel, _ := NewLightningChannel(l.Signer, l.chainIO, l.chainNotifier, res.partialState) res.chanOpen <- channel req.err <- nil @@ -1183,7 +1183,7 @@ out: // Finally, create and officially open the payment channel! // TODO(roasbeef): CreationTime once tx is 'open' - channel, _ := NewLightningChannel(l.Signer, l, l.chainNotifier, + channel, _ := NewLightningChannel(l.Signer, l.chainIO, l.chainNotifier, res.partialState) res.chanOpen <- channel } diff --git a/networktest.go b/networktest.go index 59ae5c42..36c406b2 100644 --- a/networktest.go +++ b/networktest.go @@ -545,8 +545,8 @@ func (n *networkHarness) CloseChannel(ctx context.Context, force bool) (lnrpc.Lightning_CloseChannelClient, error) { closeReq := &lnrpc.CloseChannelRequest{ - ChannelPoint: cp, - AllowForceClose: force, + ChannelPoint: cp, + Force: force, } closeRespStream, err := lnNode.CloseChannel(ctx, closeReq) if err != nil { diff --git a/peer.go b/peer.go index db8b2106..938d9ccc 100644 --- a/peer.go +++ b/peer.go @@ -219,7 +219,7 @@ func (p *peer) loadActiveChannels(chans []*channeldb.OpenChannel) error { for _, dbChan := range chans { chanID := dbChan.ChanID lnChan, err := lnwallet.NewLightningChannel(p.server.lnwallet.Signer, - p.server.lnwallet, p.server.chainNotifier, dbChan) + p.server.bio, p.server.chainNotifier, dbChan) if err != nil { return err } diff --git a/server.go b/server.go index 65420575..381a7449 100644 --- a/server.go +++ b/server.go @@ -39,9 +39,12 @@ type server struct { listeners []net.Listener peers map[int32]*peer - rpcServer *rpcServer + rpcServer *rpcServer + chainNotifier chainntnfs.ChainNotifier - lnwallet *lnwallet.LightningWallet + + bio lnwallet.BlockChainIO + lnwallet *lnwallet.LightningWallet // TODO(roasbeef): add to constructor fundingMgr *fundingManager @@ -63,7 +66,8 @@ type server struct { // newServer creates a new instance of the server which is to listen using the // passed listener address. func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier, - wallet *lnwallet.LightningWallet, chanDB *channeldb.DB) (*server, error) { + bio lnwallet.BlockChainIO, wallet *lnwallet.LightningWallet, + chanDB *channeldb.DB) (*server, error) { privKey, err := wallet.GetIdentitykey() if err != nil { @@ -80,6 +84,7 @@ func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier, serializedPubKey := privKey.PubKey().SerializeCompressed() s := &server{ + bio: bio, chainNotifier: notifier, chanDB: chanDB, fundingMgr: newFundingManager(wallet), @@ -283,7 +288,7 @@ out: } case msg := <-s.routingMgr.ChOut: msg1 := msg.(*routing.RoutingMessage) - if msg1.ReceiverID == nil{ + if msg1.ReceiverID == nil { peerLog.Critical("msg1.GetReceiverID() == nil") } receiverID := msg1.ReceiverID.ToByte32()