lnwallet: re-write and rename ReceiveReestablish to ProcessChanSyncMsg

In this commit we complete the partially completed ReceiveReestablish
method and rename it to ProcessChanSyncMsg. The new version now
properly implements retransmission as defined within BOLT#2.

Additionally, we’ve added a new case which will optimistically try and
force a resynchronization of the commitment states if we detect we can
deliver a new commitment signature sooner than later after realizing
that we need to retransmit our last revocation message when we recevied
a new state transition.
This commit is contained in:
Olaoluwa Osuntokun 2017-11-09 22:59:14 -08:00
parent 769fe5cc13
commit b5476b2767
No known key found for this signature in database
GPG Key ID: 964EA263DD637C21

@ -2972,120 +2972,111 @@ func (lc *LightningChannel) SignNextCommitment() (*btcec.Signature, []*btcec.Sig
return sig, htlcSigs, nil return sig, htlcSigs, nil
} }
// ReceiveReestablish is used to handle the remote channel reestablish message // ProcessChanSyncMsg processes a ChannelReestablish message sent by the remote
// and generate the set of updates which are have to be sent to remote side // connection upon re establishment of our connection with them. This method
// to synchronize the states of the channels. // will return a single message if we are currently out of sync, otherwise a
func (lc *LightningChannel) ReceiveReestablish(msg *lnwire.ChannelReestablish) ( // nil lnwire.Message will be returned. If it is decided that our level of
[]lnwire.Message, error) { // de-synchronization is irreconcilable, then an error indicating the issue
// will be returned. In this case that an error is returned, the channel should
// be force closed, as we cannot continue updates.
//
// One of two message sets will be returned:
//
// * CommitSig+Updates: if we have a pending remote commit which they claim to
// have not received
// * RevokeAndAck: if we sent a revocation message that they claim to have
// not received
func (lc *LightningChannel) ProcessChanSyncMsg(msg *lnwire.ChannelReestablish) ([]lnwire.Message, error) {
lc.Lock() lc.Lock()
defer lc.Unlock() defer lc.Unlock()
// We owe them a commitment if they have an un-acked commitment and the
// tip of their chain (from our Pov) is equal to what they think their
// next commit height should be.
remoteChainTip := lc.remoteCommitChain.tip()
oweCommitment := (lc.remoteCommitChain.hasUnackedCommitment() &&
msg.NextLocalCommitHeight == remoteChainTip.height)
// We owe them a revocation if the tail of our current commitment is
// one greater than what they _think_ our commitment tail is.
localChainTail := lc.localCommitChain.tail()
oweRevocation := localChainTail.height == msg.RemoteCommitTailHeight+1
// Now we'll examine the state we have, vs what was contained in the
// chain sync message. If we're de-synchronized, then we'll send a
// batch of messages which when applied will kick start the chain
// resync.
var updates []lnwire.Message var updates []lnwire.Message
// As far we store on last commitment transaction we should rely on the // If we owe the remote party a revocation message, then we'll re-send
// height of the commitment transaction in order to calculate the length. // the last revocation message that we sent. This will be the
numberRemoteCommitments := lc.remoteCommitChain.tip().height + 1 // revocation message for our prior chain tail.
if oweRevocation {
// Number of the revocations might be calculated as the height of the revocationMsg, err := lc.generateRevocation(
// commitment transactions which will be revoked next minus one. And plus localChainTail.height - 1,
// one because height starts from zero. )
numberRemoteRevocations := lc.localCommitChain.tail().height - 1 + 1
revocationsnumberDiff := msg.NextRemoteRevocationNumber - numberRemoteRevocations
if revocationsnumberDiff == 0 {
// If remote side expects as receive revocation which we already
// consider as last, than it means that they aren't received our
// last revocation message.
revocationMsg, err := lc.generateRevocation(lc.currentHeight - 1)
if err != nil { if err != nil {
return nil, err return nil, err
} }
updates = append(updates, revocationMsg) updates = append(updates, revocationMsg)
} else if revocationsnumberDiff < 0 {
// Remote node claims that it received the revoke_and_ack message // Next, as a precaution, we'll check a special edge case. If
// which we did not send. // they initiated a state transition, we sent the revocation,
return nil, errors.New("remote side claims that it haven't received " + // but died before the signature was sent. We re-transmit our
"acked revoke and ack message") // revocation, but also initiate a state transition to re-sync
// them.
if lc.localCommitChain.tip().height >
lc.remoteCommitChain.tip().height {
commitSig, htlcSigs, err := lc.SignNextCommitment()
if err != nil {
return nil, err
}
updates = append(updates, &lnwire.CommitSig{
ChanID: lnwire.NewChanIDFromOutPoint(
&lc.channelState.FundingOutpoint,
),
CommitSig: commitSig,
HtlcSigs: htlcSigs,
})
}
} else if !oweRevocation && localChainTail.height != msg.RemoteCommitTailHeight {
// If we don't owe them a revocation, and the height of our
// commitment chain reported by the remote party is not equal
// to our chain tail, then we cannot sync.
return nil, ErrCannotSyncCommitChains
} }
commitmentChainDiff := msg.NextLocalCommitmentNumber - numberRemoteCommitments // If we owe them a commitment, then we'll read from disk our
if commitmentChainDiff == 0 { // commitment diff, so we can re-send them to the remote party.
// If remote side expects as receive commitment which we already if oweCommitment {
// consider as last, than it means that they aren't received our // Grab the current remote chain tip from the database. This
// last commit sig message. // commit diff contains all the information required to re-sync
commitment := lc.remoteCommitChain.tip() // our states.
chanID := lnwire.NewChanIDFromOutPoint(&lc.channelState.FundingOutpoint) commitDiff, err := lc.channelState.RemoteCommitChainTip()
// TODO: Read from update log, which will contains settle/fail
// updates also.
for _, htlc := range commitment.outgoingHTLCs {
// If htlc is included in the local commitment chain (have been
// included by remote side) or htlc is included in remote chain, but
// not in the last commimemnt transaction than we should skip it,
// because we need resend only updates which haven't been received
// by remotes side.
if htlc.addCommitHeightLocal != 0 ||
(htlc.addCommitHeightLocal != 0 &&
htlc.addCommitHeightLocal <= commitment.height) {
continue
}
switch htlc.EntryType {
case Add:
var onionBlob [lnwire.OnionPacketSize]byte
copy(onionBlob[:], htlc.OnionBlob)
updates = append(updates, &lnwire.UpdateAddHTLC{
ChanID: chanID,
ID: htlc.Index,
Expiry: htlc.Timeout,
Amount: htlc.Amount,
PaymentHash: htlc.RHash,
OnionBlob: onionBlob,
})
case Fail:
updates = append(updates, &lnwire.UpdateFailHTLC{
ChanID: chanID,
ID: htlc.Index,
Reason: lnwire.OpaqueReason([]byte{}),
})
case MalformedFail:
updates = append(updates, &lnwire.UpdateFailMalformedHTLC{
ChanID: chanID,
ID: htlc.Index,
ShaOnionBlob: htlc.ShaOnionBlob,
FailureCode: htlc.FailCode,
})
case Settle:
updates = append(updates, &lnwire.UpdateFufillHTLC{
ChanID: chanID,
ID: htlc.Index,
PaymentPreimage: htlc.RPreimage,
})
}
}
// Generate last sent commit sig message by signing the transaction and
// creating the signature.
lc.signDesc.SigHashes = txscript.NewTxSigHashes(commitment.txn)
sig, err := lc.signer.SignOutputRaw(commitment.txn, lc.signDesc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
commitSig, err := btcec.ParseSignature(sig, btcec.S256()) // Next, we'll need to send over any updates we sent as part of
if err != nil { // this new proposed commitment state.
return nil, err for _, logUpdate := range commitDiff.LogUpdates {
updates = append(updates, logUpdate.UpdateMsg)
} }
updates = append(updates, &lnwire.CommitSig{
ChanID: chanID,
CommitSig: commitSig,
})
} else if commitmentChainDiff < 0 { // With the batch of updates accumulated, we'll now re-send the
// Remote node claims that it received the commit sig message which we // original CommitSig message required to re-sync their remote
// did not send. // commitment chain with our local version of their chain.
return nil, errors.New("remote side claims that it haven't received " + updates = append(updates, commitDiff.CommitSig)
"acked commit sig message")
} else if !oweCommitment && remoteChainTip.height+1 !=
msg.NextLocalCommitHeight {
// If we don't owe them a commitment, yet the tip of their
// chain isn't one more than the next local commit height they
// report, we'll fail the channel.
return nil, ErrCannotSyncCommitChains
} }
return updates, nil return updates, nil