Skip to content

Commit

Permalink
add ChannelReestablish message
Browse files Browse the repository at this point in the history
  • Loading branch information
nGoline committed Nov 21, 2024
1 parent 7215fe4 commit dd5cf00
Show file tree
Hide file tree
Showing 7 changed files with 360 additions and 1 deletion.
71 changes: 71 additions & 0 deletions src/NLightning.Bolts/BOLT2/Messages/ChannelReestablishMessage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System.Runtime.Serialization;

namespace NLightning.Bolts.BOLT2.Messages;

using Base;
using Bolts.Constants;
using Common.Constants;
using Common.TLVs;
using Exceptions;
using Payloads;

/// <summary>
/// Represents a channel_reestablish message.
/// </summary>
/// <remarks>
/// The channel_reestablish message is sent when a connection is lost.
/// The message type is 136.
/// </remarks>
public sealed class ChannelReestablishMessage : BaseMessage
{
/// <summary>
/// The payload of the message.
/// </summary>
public new ChannelReestablishPayload Payload { get => (ChannelReestablishPayload)base.Payload; }

public NextFundingTlv? NextFundingTlv { get; }

public ChannelReestablishMessage(ChannelReestablishPayload payload, NextFundingTlv? nextFundingTlv = null)
: base(MessageTypes.CHANNEL_REESTABLISH, payload)
{
NextFundingTlv = nextFundingTlv;

if (NextFundingTlv is not null)
{
Extension = new TlvStream();
Extension.Add(NextFundingTlv);
}
}

/// <summary>
/// Deserialize a ChannelReestablishMessage from a stream.
/// </summary>
/// <param name="stream">The stream to deserialize from.</param>
/// <returns>The deserialized ChannelReestablishMessage.</returns>
/// <exception cref="MessageSerializationException">Error deserializing ChannelReestablishMessage</exception>
public static async Task<ChannelReestablishMessage> DeserializeAsync(Stream stream)
{
try
{
// Deserialize payload
var payload = await ChannelReestablishPayload.DeserializeAsync(stream);

// Deserialize extension
var extension = await TlvStream.DeserializeAsync(stream);
if (extension is null)
{
return new ChannelReestablishMessage(payload);
}

var nextFundingTlv = extension.TryGetTlv(TlvConstants.NEXT_FUNDING, out var tlv)
? NextFundingTlv.FromTlv(tlv!)
: null;

return new ChannelReestablishMessage(payload, nextFundingTlv);
}
catch (SerializationException e)
{
throw new MessageSerializationException("Error deserializing ChannelReestablishMessage", e);
}
}
}
86 changes: 86 additions & 0 deletions src/NLightning.Bolts/BOLT2/Payloads/ChannelReestablishPayload.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using NBitcoin;

namespace NLightning.Bolts.BOLT2.Payloads;

using Common.BitUtils;
using Exceptions;
using Interfaces;

/// <summary>
/// Represents the payload for the channel_reestablish message.
/// </summary>
/// <remarks>
/// Initializes a new instance of the ChannelReestablishPayload class.
/// </remarks>
/// <param name="channelId">The channel ID.</param>
public class ChannelReestablishPayload(ChannelId channelId, ulong nextCommitmentNumber, ulong nextRevocationNumber, ReadOnlyMemory<byte> yourLastPerCommitmentSecret, PubKey myCurrentPerCommitmentPoint) : IMessagePayload
{
/// <summary>
/// Gets the channel ID.
/// </summary>
public ChannelId ChannelId { get; } = channelId;

/// <summary>
/// The commitment transaction counter
/// </summary>
public ulong NextCommitmentNumber { get; } = nextCommitmentNumber;

/// <summary>
/// The commitment counter it expects for the next revoke and ack message
/// </summary>
public ulong NextRevocationNumber { get; } = nextRevocationNumber;

/// <summary>
/// The last per commitment secret received
/// </summary>
public ReadOnlyMemory<byte> YourLastPerCommitmentSecret { get; } = yourLastPerCommitmentSecret;

/// <summary>
/// The current per commitment point
/// </summary>
public PubKey MyCurrentPerCommitmentPoint { get; } = myCurrentPerCommitmentPoint;

/// <inheritdoc/>
public async Task SerializeAsync(Stream stream)
{
await ChannelId.SerializeAsync(stream);
await stream.WriteAsync(EndianBitConverter.GetBytesBigEndian(NextCommitmentNumber));
await stream.WriteAsync(EndianBitConverter.GetBytesBigEndian(NextRevocationNumber));
await stream.WriteAsync(YourLastPerCommitmentSecret);
await stream.WriteAsync(MyCurrentPerCommitmentPoint.ToBytes());
}

/// <summary>
/// Deserializes the payload from a stream.
/// </summary>
/// <param name="stream">The stream to deserialize from.</param>
/// <returns>The deserialized payload.</returns>
/// <exception cref="PayloadSerializationException">Error deserializing Payload</exception>
public static async Task<ChannelReestablishPayload> DeserializeAsync(Stream stream)
{
try
{
var channelId = await ChannelId.DeserializeAsync(stream);

var buffer = new byte[sizeof(ulong)];
await stream.ReadExactlyAsync(buffer);
var nextCommitmentNumber = EndianBitConverter.ToUInt64BigEndian(buffer);

await stream.ReadExactlyAsync(buffer);
var nextRevocationNumber = EndianBitConverter.ToUInt64BigEndian(buffer);

var yourLastPerCommitmentSecret = new byte[32];
await stream.ReadExactlyAsync(yourLastPerCommitmentSecret);

buffer = new byte[33];
await stream.ReadExactlyAsync(buffer);
var myCurrentPerCommitmentPoint = new PubKey(buffer);

return new ChannelReestablishPayload(channelId, nextCommitmentNumber, nextRevocationNumber, yourLastPerCommitmentSecret, myCurrentPerCommitmentPoint);
}
catch (Exception e)
{
throw new PayloadSerializationException("Error deserializing ChannelReestablishPayload", e);
}
}
}
24 changes: 24 additions & 0 deletions src/NLightning.Bolts/Factories/MessageFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,29 @@ public static IMessage CreateUpdateFailMalformedHtlcMessage(ChannelId channelId,

return new UpdateFailMalformedHtlcMessage(payload);
}

/// <summary>
/// Create a ChannelReestablish message.
/// </summary>
/// <param name="channelId">The channel id.</param>
/// <param name="nextCommitmentNumber">The next commitment number.</param>
/// <param name="nextRevocationNumber">The next revocation number.</param>
/// <param name="yourLastPerCommitmentSecret">The peer last per commitment secret.</param>
/// <param name="myCurrentPerCommitmentPoint">Our current per commitment point.</param>
/// <returns>The ChannelReestablish message.</returns>
/// <seealso cref="ChannelReestablishMessage"/>
/// <seealso cref="ChannelId"/>
/// <seealso cref="ChannelReestablishPayload"/>
public static IMessage CreateChannelReestablishMessage(ChannelId channelId, ulong nextCommitmentNumber,
ulong nextRevocationNumber,
ReadOnlyMemory<byte> yourLastPerCommitmentSecret,
PubKey myCurrentPerCommitmentPoint)
{
var payload = new ChannelReestablishPayload(channelId, nextCommitmentNumber, nextRevocationNumber,
yourLastPerCommitmentSecret, myCurrentPerCommitmentPoint);

return new ChannelReestablishMessage(payload);
}
#endregion

/// <summary>
Expand Down Expand Up @@ -643,6 +666,7 @@ public static async Task<IMessage> DeserializeMessageAsync(MemoryStream stream)
MessageTypes.REVOKE_AND_ACK => await RevokeAndAckMessage.DeserializeAsync(stream), // 133 -> 0x85
MessageTypes.UPDATE_FEE => await UpdateFeeMessage.DeserializeAsync(stream), // 134 -> 0x86
MessageTypes.UPDATE_FAIL_MALFORMED_HTLC => await UpdateFailMalformedHtlcMessage.DeserializeAsync(stream), // 135 -> 0x87
MessageTypes.CHANNEL_REESTABLISH => await ChannelReestablishMessage.DeserializeAsync(stream), // 136 -> 0x88

MessageTypes.OPEN_CHANNEL => throw new InvalidMessageException("You must use OpenChannel2 flow"),
MessageTypes.ACCEPT_CHANNEL => throw new InvalidMessageException("You must use OpenChannel2 flow"),
Expand Down
8 changes: 8 additions & 0 deletions src/NLightning.Common/Constants/TLVConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,12 @@ public static class TlvConstants
/// The "Blinded Path" is used in the UpdateAddHtlcMessage
/// </remarks>
public static readonly BigSize BLINDED_PATH = 0;

/// <summary>
/// Next Funding TLV Type
/// </summary>
/// <remarks>
/// The "Next Funding" is used in the ChannelReestablishMessage
/// </remarks>
public static readonly BigSize NEXT_FUNDING = 0;
}
2 changes: 1 addition & 1 deletion src/NLightning.Common/TLVs/BlindedPathTlv.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace NLightning.Common.TLVs;
public class BlindedPathTlv : Tlv
{
/// <summary>
/// The channel type
/// The blinded path key
/// </summary>
public PubKey PathKey { get; }

Expand Down
47 changes: 47 additions & 0 deletions src/NLightning.Common/TLVs/NextFundingTlv.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
namespace NLightning.Common.TLVs;

using Constants;
using Types;

/// <summary>
/// Blinded Path TLV.
/// </summary>
/// <remarks>
/// The blinded path TLV is used in the UpdateAddHtlcMessage to communicate the blinded path key.
/// </remarks>
public class NextFundingTlv : Tlv
{
/// <summary>
/// The blinded path key
/// </summary>
public byte[] NextFundingTxId { get; }

public NextFundingTlv(byte[] nextFundingTxId) : base(TlvConstants.NEXT_FUNDING)
{
NextFundingTxId = nextFundingTxId;

Value = NextFundingTxId;
Length = Value.Length;
}

/// <summary>
/// Cast NextFundingTlv from a Tlv.
/// </summary>
/// <param name="tlv">The tlv to cast from.</param>
/// <returns>The cast NextFundingTlv.</returns>
/// <exception cref="InvalidCastException">Error casting NextFundingTlv</exception>
public static NextFundingTlv FromTlv(Tlv tlv)
{
if (tlv.Type != TlvConstants.NEXT_FUNDING)
{
throw new InvalidCastException("Invalid TLV type");
}

if (tlv.Length != 32)
{
throw new InvalidCastException("Invalid length");
}

return new NextFundingTlv(tlv.Value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
using NBitcoin;

namespace NLightning.Bolts.Tests.BOLT2.Messages;

using Bolts.BOLT2.Messages;
using Bolts.BOLT2.Payloads;
using Common.TLVs;
using Common.Types;
using Exceptions;
using Utils;

public class ChannelReestablishMessageTests
{
#region Deserialize
[Fact]
public async Task Given_ValidStream_When_DeserializeAsync_Then_ReturnsChannelReestablishMessage()
{
// Arrange
var expectedChannelId = ChannelId.Zero;
var expectedNextCommitmentNumber = 1UL;
var expectedNextRevocationNumber = 2UL;
var expectedYourLastPerCommitmentSecret = "567cbdadb00b825448b2e414487d73a97f657f0634166d3ab3f3a2cc1042eda5".ToByteArray();
var expectedMyCurrentPerCommitmentPoint = new PubKey("02c93ca7dca44d2e45e3cc5419d92750f7fb3a0f180852b73a621f4051c0193a75".ToByteArray());
var stream = new MemoryStream("000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000002567CBDADB00B825448B2E414487D73A97F657F0634166D3AB3F3A2CC1042EDA502C93CA7DCA44D2E45E3CC5419D92750F7FB3A0F180852B73A621F4051C0193A75".ToByteArray());

// Act
var message = await ChannelReestablishMessage.DeserializeAsync(stream);

// Assert
Assert.Equal(expectedChannelId, message.Payload.ChannelId);
Assert.Equal(expectedNextCommitmentNumber, message.Payload.NextCommitmentNumber);
Assert.Equal(expectedNextRevocationNumber, message.Payload.NextRevocationNumber);
Assert.Equal(expectedYourLastPerCommitmentSecret, message.Payload.YourLastPerCommitmentSecret);
Assert.Equal(expectedMyCurrentPerCommitmentPoint, message.Payload.MyCurrentPerCommitmentPoint);
Assert.Null(message.Extension);
}

[Fact]
public async Task Given_ValidStream_When_DeserializeAsync_Then_ReturnsChannelReestablishMessageWithExtensions()
{
// Arrange
var expectedChannelId = ChannelId.Zero;
var expectedNextCommitmentNumber = 1UL;
var expectedNextRevocationNumber = 2UL;
var expectedYourLastPerCommitmentSecret = "567cbdadb00b825448b2e414487d73a97f657f0634166d3ab3f3a2cc1042eda5".ToByteArray();
var expectedMyCurrentPerCommitmentPoint = new PubKey("02c93ca7dca44d2e45e3cc5419d92750f7fb3a0f180852b73a621f4051c0193a75".ToByteArray());
var nextFundingTlv = new NextFundingTlv("567cbdadb00b825448b2e414487d73a97f657f0634166d3ab3f3a2cc1042eda5".ToByteArray());
var stream = new MemoryStream("000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000002567CBDADB00B825448B2E414487D73A97F657F0634166D3AB3F3A2CC1042EDA502C93CA7DCA44D2E45E3CC5419D92750F7FB3A0F180852B73A621F4051C0193A750020567CBDADB00B825448B2E414487D73A97F657F0634166D3AB3F3A2CC1042EDA5".ToByteArray());

// Act
var message = await ChannelReestablishMessage.DeserializeAsync(stream);

// Assert
Assert.Equal(expectedChannelId, message.Payload.ChannelId);
Assert.Equal(expectedNextCommitmentNumber, message.Payload.NextCommitmentNumber);
Assert.Equal(expectedNextRevocationNumber, message.Payload.NextRevocationNumber);
Assert.Equal(expectedYourLastPerCommitmentSecret, message.Payload.YourLastPerCommitmentSecret);
Assert.Equal(expectedMyCurrentPerCommitmentPoint, message.Payload.MyCurrentPerCommitmentPoint);
Assert.NotNull(message.Extension);
Assert.NotNull(message.NextFundingTlv);
Assert.Equal(nextFundingTlv, message.NextFundingTlv);
}

[Fact]
public async Task Given_InvalidStreamContent_When_DeserializeAsync_Then_ThrowsMessageSerializationException()
{
// Arrange
var invalidStream = new MemoryStream("000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000002567CBDADB00B825448B2E414487D73A97F657F0634166D3AB3F3A2CC1042EDA502C93CA7DCA44D2E45E3CC5419D92750F7FB3A0F180852B73A621F4051C0193A750002".ToByteArray());

// Act & Assert
await Assert.ThrowsAsync<MessageSerializationException>(() => ChannelReestablishMessage.DeserializeAsync(invalidStream));
}
#endregion

#region Serialize
[Fact]
public async Task Given_ValidPayload_When_SerializeAsync_Then_WritesCorrectDataToStream()
{
// Arrange
var channelId = ChannelId.Zero;
var nextCommitmentNumber = 1UL;
var nextRevocationNumber = 2UL;
var yourLastPerCommitmentSecret = "567cbdadb00b825448b2e414487d73a97f657f0634166d3ab3f3a2cc1042eda5".ToByteArray();
var myCurrentPerCommitmentPoint = new PubKey("02c93ca7dca44d2e45e3cc5419d92750f7fb3a0f180852b73a621f4051c0193a75".ToByteArray());
var message = new ChannelReestablishMessage(new ChannelReestablishPayload(channelId, nextCommitmentNumber, nextRevocationNumber, yourLastPerCommitmentSecret, myCurrentPerCommitmentPoint));
var stream = new MemoryStream();
var expectedBytes = "0088000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000002567CBDADB00B825448B2E414487D73A97F657F0634166D3AB3F3A2CC1042EDA502C93CA7DCA44D2E45E3CC5419D92750F7FB3A0F180852B73A621F4051C0193A75".ToByteArray();

// Act
await message.SerializeAsync(stream);
stream.Position = 0;
var result = new byte[stream.Length];
_ = await stream.ReadAsync(result);

// Assert
Assert.Equal(expectedBytes, result);
}

[Fact]
public async Task Given_ValidExtensions_When_SerializeAsync_Then_WritesCorrectDataToStream()
{
// Arrange
var channelId = ChannelId.Zero;
var nextCommitmentNumber = 1UL;
var nextRevocationNumber = 2UL;
var yourLastPerCommitmentSecret = "567cbdadb00b825448b2e414487d73a97f657f0634166d3ab3f3a2cc1042eda5".ToByteArray();
var myCurrentPerCommitmentPoint = new PubKey("02c93ca7dca44d2e45e3cc5419d92750f7fb3a0f180852b73a621f4051c0193a75".ToByteArray());
var nextFundingTlv = new NextFundingTlv("567cbdadb00b825448b2e414487d73a97f657f0634166d3ab3f3a2cc1042eda5".ToByteArray());
var message = new ChannelReestablishMessage(new ChannelReestablishPayload(channelId, nextCommitmentNumber, nextRevocationNumber, yourLastPerCommitmentSecret, myCurrentPerCommitmentPoint), nextFundingTlv);
var stream = new MemoryStream();
var expectedBytes = "0088000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000002567CBDADB00B825448B2E414487D73A97F657F0634166D3AB3F3A2CC1042EDA502C93CA7DCA44D2E45E3CC5419D92750F7FB3A0F180852B73A621F4051C0193A750020567CBDADB00B825448B2E414487D73A97F657F0634166D3AB3F3A2CC1042EDA5".ToByteArray();

// Act
await message.SerializeAsync(stream);
stream.Position = 0;
var result = new byte[stream.Length];
_ = await stream.ReadAsync(result);

// Assert
Assert.Equal(expectedBytes, result);
}
#endregion
}

0 comments on commit dd5cf00

Please sign in to comment.