package lnwire import ( "bytes" "io" "github.com/btcsuite/btcd/btcec/v2" "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/tlv" ) const ( CRDynHeight tlv.Type = 20 ) // DynHeight is a newtype wrapper to get the proper RecordProducer instance // to smoothly integrate with the ChannelReestablish Message instance. type DynHeight uint64 // Record implements the RecordProducer interface, allowing a full tlv.Record // object to be constructed from a DynHeight. func (d *DynHeight) Record() tlv.Record { return tlv.MakePrimitiveRecord(CRDynHeight, (*uint64)(d)) } // ChannelReestablish is a message sent between peers that have an existing // open channel upon connection reestablishment. This message allows both sides // to report their local state, and their current knowledge of the state of the // remote commitment chain. If a deviation is detected and can be recovered // from, then the necessary messages will be retransmitted. If the level of // desynchronization is irreconcilable, then the channel will be force closed. type ChannelReestablish struct { // ChanID is the channel ID of the channel state we're attempting to // synchronize with the remote party. ChanID ChannelID // NextLocalCommitHeight is the next local commitment height of the // sending party. If the height of the sender's commitment chain from // the receiver's Pov is one less that this number, then the sender // should re-send the *exact* same proposed commitment. // // In other words, the receiver should re-send their last sent // commitment iff: // // * NextLocalCommitHeight == remoteCommitChain.Height // // This covers the case of a lost commitment which was sent by the // sender of this message, but never received by the receiver of this // message. NextLocalCommitHeight uint64 // RemoteCommitTailHeight is the height of the receiving party's // unrevoked commitment from the PoV of the sender of this message. If // the height of the receiver's commitment is *one more* than this // value, then their prior RevokeAndAck message should be // retransmitted. // // In other words, the receiver should re-send their last sent // RevokeAndAck message iff: // // * localCommitChain.tail().Height == RemoteCommitTailHeight + 1 // // This covers the case of a lost revocation, wherein the receiver of // the message sent a revocation for a prior state, but the sender of // the message never fully processed it. RemoteCommitTailHeight uint64 // LastRemoteCommitSecret is the last commitment secret that the // receiving node has sent to the sending party. This will be the // secret of the last revoked commitment transaction. Including this // provides proof that the sending node at least knows of this state, // as they couldn't have produced it if it wasn't sent, as the value // can be authenticated by querying the shachain or the receiving // party. LastRemoteCommitSecret [32]byte // LocalUnrevokedCommitPoint is the commitment point used in the // current un-revoked commitment transaction of the sending party. LocalUnrevokedCommitPoint *btcec.PublicKey // LocalNonce is an optional field that stores a local musig2 nonce. // This will only be populated if the simple taproot channels type was // negotiated. // LocalNonce OptMusig2NonceTLV // DynHeight is an optional field that stores the dynamic commitment // negotiation height that is incremented upon successful completion of // a dynamic commitment negotiation DynHeight fn.Option[DynHeight] // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can // be used to specify optional data such as custom TLV fields. ExtraData ExtraOpaqueData } // A compile time check to ensure ChannelReestablish implements the // lnwire.Message interface. var _ Message = (*ChannelReestablish)(nil) // Encode serializes the target ChannelReestablish into the passed io.Writer // observing the protocol version specified. // // This is part of the lnwire.Message interface. func (a *ChannelReestablish) Encode(w *bytes.Buffer, pver uint32) error { if err := WriteChannelID(w, a.ChanID); err != nil { return err } if err := WriteUint64(w, a.NextLocalCommitHeight); err != nil { return err } if err := WriteUint64(w, a.RemoteCommitTailHeight); err != nil { return err } // If the commit point wasn't sent, then we won't write out any of the // remaining fields as they're optional. if a.LocalUnrevokedCommitPoint == nil { // However, we'll still write out the extra data if it's // present. // // NOTE: This is here primarily for the quickcheck tests, in // practice, we'll always populate this field. return WriteBytes(w, a.ExtraData) } // Otherwise, we'll write out the remaining elements. if err := WriteBytes(w, a.LastRemoteCommitSecret[:]); err != nil { return err } if err := WritePublicKey(w, a.LocalUnrevokedCommitPoint); err != nil { return err } recordProducers := make([]tlv.RecordProducer, 0, 1) a.LocalNonce.WhenSome(func(localNonce Musig2NonceTLV) { recordProducers = append(recordProducers, &localNonce) }) a.DynHeight.WhenSome(func(h DynHeight) { recordProducers = append(recordProducers, &h) }) err := EncodeMessageExtraData(&a.ExtraData, recordProducers...) if err != nil { return err } return WriteBytes(w, a.ExtraData) } // Decode deserializes a serialized ChannelReestablish stored in the passed // io.Reader observing the specified protocol version. // // This is part of the lnwire.Message interface. func (a *ChannelReestablish) Decode(r io.Reader, pver uint32) error { err := ReadElements(r, &a.ChanID, &a.NextLocalCommitHeight, &a.RemoteCommitTailHeight, ) if err != nil { return err } // This message has currently defined optional fields. As a result, // we'll only proceed if there's still bytes remaining within the // reader. // // We'll manually parse out the optional fields in order to be able to // still utilize the io.Reader interface. // We'll first attempt to read the optional commit secret, if we're at // the EOF, then this means the field wasn't included so we can exit // early. var buf [32]byte _, err = io.ReadFull(r, buf[:32]) if err == io.EOF { // If there aren't any more bytes, then we'll emplace an empty // extra data to make our quickcheck tests happy. a.ExtraData = make([]byte, 0) return nil } else if err != nil { return err } // If the field is present, then we'll copy it over and proceed. copy(a.LastRemoteCommitSecret[:], buf[:]) // We'll conclude by parsing out the commitment point. We don't check // the error in this case, as it has included the commit secret, then // they MUST also include the commit point. if err = ReadElement(r, &a.LocalUnrevokedCommitPoint); err != nil { return err } var tlvRecords ExtraOpaqueData if err := ReadElements(r, &tlvRecords); err != nil { return err } var ( dynHeight DynHeight localNonce = a.LocalNonce.Zero() ) typeMap, err := tlvRecords.ExtractRecords( &localNonce, &dynHeight, ) if err != nil { return err } if val, ok := typeMap[a.LocalNonce.TlvType()]; ok && val == nil { a.LocalNonce = tlv.SomeRecordT(localNonce) } if val, ok := typeMap[CRDynHeight]; ok && val == nil { a.DynHeight = fn.Some(dynHeight) } if len(tlvRecords) != 0 { a.ExtraData = tlvRecords } return nil } // MsgType returns the integer uniquely identifying this message type on the // wire. // // This is part of the lnwire.Message interface. func (a *ChannelReestablish) MsgType() MessageType { return MsgChannelReestablish }