lnwallet: add ability to trigger a force closure within channel state machine

This commit introduces the concept of a manually initiated “force”
closer within the channel state machine. A force closure is a closure
initiated by a  local subsystem which broadcasts the current commitment
state directly on-chain rather than attempting to cooperatively
negotiate a closure with the remote party.

A force closure returns a ForceCloseSummary which includes all the
details required for claiming all rightfully owned outputs within the
broadcast commitment transaction.

Additionally two new publicly exported channels are introduced, one
which is closed due a locally initiated force closure, and the other
which is closed once we detect that the remote party has executed a
unilateral closure by broadcasting their version of the commitment
transaction.
This commit is contained in:
Olaoluwa Osuntokun 2016-09-12 12:33:22 -07:00
parent 1a357755d7
commit d0353b2864
No known key found for this signature in database
GPG Key ID: 9CC5B105D03521A2
8 changed files with 218 additions and 44 deletions

View File

@ -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.

2
lnd.go
View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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()