diff --git a/src/Lantern.Beacon/BeaconClient.cs b/src/Lantern.Beacon/BeaconClient.cs index 69513a4..5574523 100644 --- a/src/Lantern.Beacon/BeaconClient.cs +++ b/src/Lantern.Beacon/BeaconClient.cs @@ -4,8 +4,6 @@ using Lantern.Beacon.Networking.Gossip; using Lantern.Beacon.Storage; using Lantern.Beacon.Sync; -using Lantern.Beacon.Sync.Helpers; -using Lantern.Beacon.Sync.Processors; using Lantern.Beacon.Sync.Types.Ssz.Altair; using Lantern.Beacon.Sync.Types.Ssz.Capella; using Lantern.Beacon.Sync.Types.Ssz.Deneb; @@ -19,7 +17,9 @@ public class BeaconClient(ISyncProtocol syncProtocol, ILiteDbService liteDbServi { private readonly ILogger _logger = serviceProvider.GetRequiredService().CreateLogger(); - public async Task InitAsync(CancellationToken token = default) + public CancellationTokenSource? CancellationTokenSource { get; private set; } + + public async Task InitAsync() { try { @@ -35,12 +35,12 @@ public async Task InitAsync(CancellationToken token = default) peerState.Init(peerFactoryBuilder.AppLayerProtocols); gossipSubManager.Init(); - if (gossipSubManager.LightClientFinalityUpdate == null || gossipSubManager.LightClientOptimisticUpdate == null) + if (gossipSubManager.LightClientFinalityUpdate == null && gossipSubManager.LightClientOptimisticUpdate == null) { return; } - await beaconClientManager.InitAsync(token); + await beaconClientManager.InitAsync(); } catch (Exception e) { @@ -53,6 +53,8 @@ public async Task StartAsync(CancellationToken token = default) { try { + CancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token); + await gossipSubManager.StartAsync(token); await beaconClientManager.StartAsync(token); } @@ -65,8 +67,18 @@ public async Task StartAsync(CancellationToken token = default) public async Task StopAsync() { + if (CancellationTokenSource == null) + { + _logger.LogWarning("Beacon client is not running. Nothing to stop"); + return; + } + + await CancellationTokenSource.CancelAsync(); await gossipSubManager.StopAsync(); await beaconClientManager.StopAsync(); + liteDbService.Dispose(); + CancellationTokenSource.Dispose(); + CancellationTokenSource = null; } } \ No newline at end of file diff --git a/src/Lantern.Beacon/BeaconClientManager.cs b/src/Lantern.Beacon/BeaconClientManager.cs index 55008a0..653154f 100644 --- a/src/Lantern.Beacon/BeaconClientManager.cs +++ b/src/Lantern.Beacon/BeaconClientManager.cs @@ -31,7 +31,7 @@ public class BeaconClientManager(BeaconClientOptions clientOptions, public CancellationTokenSource? CancellationTokenSource { get; private set; } public ILocalPeer? LocalPeer { get; private set; } - public async Task InitAsync(CancellationToken token = default) + public async Task InitAsync() { try { diff --git a/src/Lantern.Beacon/BeaconClientPeerFactory.cs b/src/Lantern.Beacon/BeaconClientPeerFactory.cs index 8c9820c..73974c1 100644 --- a/src/Lantern.Beacon/BeaconClientPeerFactory.cs +++ b/src/Lantern.Beacon/BeaconClientPeerFactory.cs @@ -20,10 +20,12 @@ public override ILocalPeer Create(Identity? identity = null, Multiaddress? local { identity ??= new Identity(); localAddr ??= $"/ip4/0.0.0.0/tcp/0/p2p/{identity.PeerId}"; + if (localAddr.Get() is null) { localAddr.Add(identity.PeerId.ToString()); } + return base.Create(identity, localAddr); } } \ No newline at end of file diff --git a/src/Lantern.Beacon/BeaconClientServiceBuilder.cs b/src/Lantern.Beacon/BeaconClientServiceBuilder.cs index bc9a510..fbbf605 100644 --- a/src/Lantern.Beacon/BeaconClientServiceBuilder.cs +++ b/src/Lantern.Beacon/BeaconClientServiceBuilder.cs @@ -15,11 +15,11 @@ namespace Lantern.Beacon; public class BeaconClientServiceBuilder(IServiceCollection services) : IBeaconClientServiceBuilder { - private readonly IDiscv5ProtocolBuilder? _discv5ProtocolBuilder = new Discv5ProtocolBuilder(services); private SyncProtocolOptions _syncProtocolOptions = new(); private BeaconClientOptions _beaconClientOptions = new(); - private ILoggerFactory _loggerFactory = LoggingOptions.Default; + private IDiscv5ProtocolBuilder _discv5ProtocolBuilder = new Discv5ProtocolBuilder(services); private IServiceProvider? _serviceProvider; + private ILoggerFactory _loggerFactory = LoggingOptions.Default; public IBeaconClientServiceBuilder AddDiscoveryProtocol(Action configure) { @@ -27,8 +27,7 @@ public IBeaconClientServiceBuilder AddDiscoveryProtocol(Action factorySetup) + public IBeaconClientServiceBuilder AddLibp2pProtocol(Func factorySetup) { services.AddScoped(sp => factorySetup(new BeaconClientPeerFactoryBuilder(sp))) .AddScoped() @@ -75,11 +74,6 @@ public IBeaconClientServiceBuilder WithLoggerFactory(ILoggerFactory loggerFactor public IBeaconClient Build() { - if(_discv5ProtocolBuilder == null) - { - throw new ArgumentNullException(nameof(_discv5ProtocolBuilder)); - } - services.AddBeaconClient(_discv5ProtocolBuilder.Build(), _beaconClientOptions, _syncProtocolOptions, _loggerFactory); _serviceProvider = services.BuildServiceProvider(); diff --git a/src/Lantern.Beacon/BeaconClientUtility.cs b/src/Lantern.Beacon/BeaconClientUtility.cs index 98b0bb9..8207fee 100644 --- a/src/Lantern.Beacon/BeaconClientUtility.cs +++ b/src/Lantern.Beacon/BeaconClientUtility.cs @@ -56,8 +56,23 @@ private static bool TryGetIpAndPort(IEnr enr, string ipKey, string portKey, out return false; } - ip = enr.GetEntry(ipKey).Value; - port = enr.GetEntry(portKey).Value; + if(ipKey == EnrEntryKey.Ip) + { + ip = enr.GetEntry(ipKey).Value; + } + else if(ipKey == EnrEntryKey.Ip6) + { + ip = enr.GetEntry(ipKey).Value; + } + + if(portKey == EnrEntryKey.Tcp) + { + port = enr.GetEntry(portKey).Value; + } + else if(portKey == EnrEntryKey.Tcp6) + { + port = enr.GetEntry(portKey).Value; + } return true; } diff --git a/src/Lantern.Beacon/IBeaconClient.cs b/src/Lantern.Beacon/IBeaconClient.cs index a4f0b60..78971b9 100644 --- a/src/Lantern.Beacon/IBeaconClient.cs +++ b/src/Lantern.Beacon/IBeaconClient.cs @@ -2,7 +2,9 @@ namespace Lantern.Beacon; public interface IBeaconClient { - Task InitAsync(CancellationToken token = default); + Task InitAsync(); Task StartAsync(CancellationToken token = default); + + Task StopAsync(); } \ No newline at end of file diff --git a/src/Lantern.Beacon/IBeaconClientManager.cs b/src/Lantern.Beacon/IBeaconClientManager.cs index f62bcdb..ad3f649 100644 --- a/src/Lantern.Beacon/IBeaconClientManager.cs +++ b/src/Lantern.Beacon/IBeaconClientManager.cs @@ -8,7 +8,7 @@ public interface IBeaconClientManager ILocalPeer? LocalPeer { get; } - Task InitAsync(CancellationToken token = default); + Task InitAsync(); Task StartAsync(CancellationToken token = default); diff --git a/test/Lantern.Beacon.Tests/BeaconChainUtilityTests.cs b/test/Lantern.Beacon.Tests/BeaconChainUtilityTests.cs index be926e0..db0101d 100644 --- a/test/Lantern.Beacon.Tests/BeaconChainUtilityTests.cs +++ b/test/Lantern.Beacon.Tests/BeaconChainUtilityTests.cs @@ -1,3 +1,4 @@ +using System.Net; using Lantern.Beacon; using Lantern.Beacon.Sync; using Lantern.Beacon.Sync.Config; @@ -5,6 +6,7 @@ using Lantern.Discv5.Enr; using Lantern.Discv5.Enr.Entries; using Lantern.Discv5.Enr.Identity.V4; +using Lantern.Discv5.WireProtocol.Session; using Multiformats.Address.Protocols; using NUnit.Framework; using SszSharp; @@ -15,18 +17,43 @@ namespace Lantern.Beacon.Tests; public class BeaconChainUtilityTests { [Test] - public void ConvertToMultiAddress_ShouldCorrectlyConvertToMultiAddress() + public void ConvertToMultiAddress_ShouldCorrectlyConvertToMultiAddressForIpV4() { var enrRegistry = new EnrEntryRegistry(); var enrString = "enr:-Mq4QLyFLj2R0kwCmxNgO02F2JqHOUAT9CnqK9qHBwJWPlvNR36e9YydkUzFM69E0dzX7hrpOUAJVKsBLb3PysSz-IiGAY7D6Sg4h2F0dG5ldHOIAAAAAAAAAAaEZXRoMpBqlaGpBAAAAP__________gmlkgnY0gmlwhCJkw5SJc2VjcDI1NmsxoQMc6eWKtIsR4Ref474zOEeRKEuHzxrK_jffZrkzzYSuUYhzeW5jbmV0cwCDdGNwgjLIg3VkcILLBIR1ZHA2gi7g"; - var enr = new EnrFactory(enrRegistry).CreateFromString(enrString, new IdentityVerifierV4()); var multiAddress = BeaconClientUtility.ConvertToMultiAddress(enr); Assert.That(multiAddress, Is.Not.Null); + Assert.That(multiAddress.ToString().Contains("ip4"), Is.True); Assert.That(enr.GetEntry(EnrEntryKey.Ip).Value, Is.EqualTo(multiAddress!.Get().Value)); Assert.That(enr.GetEntry(EnrEntryKey.Tcp).Value, Is.EqualTo(multiAddress.Get().Value)); } + + [Test] + public void ConvertToMultiAddress_ShouldCorrectlyConvertToMultiAddressForIpV6() + { + var enrBuilder = new EnrBuilder(); + var sessionOptions = SessionOptions.Default; + + enrBuilder.WithIdentityScheme(sessionOptions.Verifier, sessionOptions.Signer); + enrBuilder.WithEntry(EnrEntryKey.Id, new EntryId("v4")); + enrBuilder.WithEntry(EnrEntryKey.Secp256K1, new EntrySecp256K1(sessionOptions.Signer.PublicKey)); + enrBuilder.WithEntry(EnrEntryKey.Ip6, new EntryIp6(IPAddress.IPv6Any)); + enrBuilder.WithEntry(EnrEntryKey.Tcp6, new EntryTcp6(30303)); + + var enr = enrBuilder.Build(); + var multiAddress = BeaconClientUtility.ConvertToMultiAddress(enr); + + Assert.That(multiAddress.ToString().Contains("ip6"), Is.True); + } + + [Test] + public void ConvertToMultiAddress_ShouldReturnIfEnrIsNull() + { + var multiAddress = BeaconClientUtility.ConvertToMultiAddress(null); + Assert.That(multiAddress, Is.Null); + } [Test] public void GetForkDigestBytes_ShouldReturnCorrectForkDigest() diff --git a/test/Lantern.Beacon.Tests/BeaconClientManagerTests.cs b/test/Lantern.Beacon.Tests/BeaconClientManagerTests.cs index 176c8a5..c3e8053 100644 --- a/test/Lantern.Beacon.Tests/BeaconClientManagerTests.cs +++ b/test/Lantern.Beacon.Tests/BeaconClientManagerTests.cs @@ -680,7 +680,7 @@ public async Task RunSyncProtocol_ShouldRunBootstrapProtocolIfSyncProtocolIsNotI var peersToDialField = typeof(BeaconClientManager).GetField("_peersToDial", BindingFlags.NonPublic | BindingFlags.Instance); var peersToDialQueue = (ConcurrentQueue)peersToDialField.GetValue(_beaconClientManager); - await _beaconClientManager.InitAsync(cts.Token); + await _beaconClientManager.InitAsync(); _beaconClientManager.StartAsync(cts.Token); @@ -743,7 +743,7 @@ public async Task RunSyncProtocol_ShouldDisconnectFromPeerIfSyncProtocolDidNotIn var peersToDialField = typeof(BeaconClientManager).GetField("_peersToDial", BindingFlags.NonPublic | BindingFlags.Instance); var peersToDialQueue = (ConcurrentQueue)peersToDialField.GetValue(_beaconClientManager); - await _beaconClientManager.InitAsync(cts.Token); + await _beaconClientManager.InitAsync(); _beaconClientManager.StartAsync(cts.Token); @@ -808,7 +808,7 @@ public async Task RunSyncProtocol_ShouldSyncDenebForkIfActiveForkIsSetToDeneb() var peersToDialField = typeof(BeaconClientManager).GetField("_peersToDial", BindingFlags.NonPublic | BindingFlags.Instance); var peersToDialQueue = (ConcurrentQueue)peersToDialField.GetValue(_beaconClientManager); - await _beaconClientManager.InitAsync(cts.Token); + await _beaconClientManager.InitAsync(); _beaconClientManager.StartAsync(cts.Token); await Task.Delay(1000, cts.Token); @@ -870,7 +870,7 @@ public async Task RunSyncProtocol_ShouldSyncCapellaForkIfActiveForkIsSetToCapell var peersToDialField = typeof(BeaconClientManager).GetField("_peersToDial", BindingFlags.NonPublic | BindingFlags.Instance); var peersToDialQueue = (ConcurrentQueue)peersToDialField.GetValue(_beaconClientManager); - await _beaconClientManager.InitAsync(cts.Token); + await _beaconClientManager.InitAsync(); _beaconClientManager.StartAsync(cts.Token); await Task.Delay(1000, cts.Token); @@ -932,7 +932,7 @@ public async Task RunSyncProtocol_ShouldBellatrixForkIfActiveForkIsSetToBellatri var peersToDialField = typeof(BeaconClientManager).GetField("_peersToDial", BindingFlags.NonPublic | BindingFlags.Instance); var peersToDialQueue = (ConcurrentQueue)peersToDialField.GetValue(_beaconClientManager); - await _beaconClientManager.InitAsync(cts.Token); + await _beaconClientManager.InitAsync(); _beaconClientManager.StartAsync(cts.Token); await Task.Delay(1000, cts.Token); @@ -994,7 +994,7 @@ public async Task RunSyncProtocol_ShouldAltairForkIfActiveForkIsSetToAltair() var peersToDialField = typeof(BeaconClientManager).GetField("_peersToDial", BindingFlags.NonPublic | BindingFlags.Instance); var peersToDialQueue = (ConcurrentQueue)peersToDialField.GetValue(_beaconClientManager); - await _beaconClientManager.InitAsync(cts.Token); + await _beaconClientManager.InitAsync(); _beaconClientManager.StartAsync(cts.Token); await Task.Delay(1000, cts.Token); diff --git a/test/Lantern.Beacon.Tests/BeaconClientPeerFactoryTests.cs b/test/Lantern.Beacon.Tests/BeaconClientPeerFactoryTests.cs new file mode 100644 index 0000000..fda5ea4 --- /dev/null +++ b/test/Lantern.Beacon.Tests/BeaconClientPeerFactoryTests.cs @@ -0,0 +1,46 @@ +using System.Reflection; +using Lantern.Beacon.Networking.Libp2pProtocols.Identify; +using Moq; +using Nethermind.Libp2p.Core; +using NUnit.Framework; + +namespace Lantern.Beacon.Tests; + +[TestFixture] +public class BeaconClientPeerFactoryTests +{ + private BeaconClientPeerFactory _beaconClientPeerFactory; + + [SetUp] + public void Setup() + { + _beaconClientPeerFactory = new BeaconClientPeerFactory(null); + } + + [Test] + public async Task ConnectedTo_ShouldDialPeerIdentifyProtocol() + { + var mockRemotePeer = new Mock(); + var method = typeof(BeaconClientPeerFactory).GetMethod("ConnectedTo", BindingFlags.NonPublic | BindingFlags.Instance); + + if (method == null) + { + Assert.Fail("The method 'ConnectedTo' was not found."); + } + else + { + var task = (Task)method.Invoke(_beaconClientPeerFactory, [mockRemotePeer.Object, true]); + await task; + } + + mockRemotePeer.Verify(x => x.DialAsync(new CancellationToken()), Times.Once()); + } + + [Test] + public void Create_ShouldReturnLocalPeer() + { + var localPeer = _beaconClientPeerFactory.Create(); + + Assert.That(localPeer, Is.Not.Null); + } +} \ No newline at end of file diff --git a/test/Lantern.Beacon.Tests/BeaconClientServiceBuilderTests.cs b/test/Lantern.Beacon.Tests/BeaconClientServiceBuilderTests.cs new file mode 100644 index 0000000..3bc5a4f --- /dev/null +++ b/test/Lantern.Beacon.Tests/BeaconClientServiceBuilderTests.cs @@ -0,0 +1,154 @@ +using Lantern.Beacon.Sync; +using Lantern.Discv5.Enr; +using Lantern.Discv5.Enr.Entries; +using Lantern.Discv5.WireProtocol; +using Lantern.Discv5.WireProtocol.Connection; +using Lantern.Discv5.WireProtocol.Session; +using Lantern.Discv5.WireProtocol.Table; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Console; +using Moq; +using Nethermind.Libp2p.Core; +using Nethermind.Libp2p.Protocols; +using Nethermind.Libp2p.Protocols.Pubsub; +using NUnit.Framework; + +namespace Lantern.Beacon.Tests; + +[TestFixture] +public class BeaconClientServiceBuilderTests +{ + private Mock _mockServicesCollection; + private Mock? _mockDiscv5ProtocolBuilder; + private BeaconClientServiceBuilder _builder; + + [SetUp] + public void Setup() + { + _mockServicesCollection = new Mock(); + _mockDiscv5ProtocolBuilder = new Mock(); + _builder = new BeaconClientServiceBuilder(_mockServicesCollection.Object); + } + + [Test] + public void AddDiscoveryProtocol_WhenCalled_ShouldConfigureDiscv5ProtocolBuilder() + { + typeof(BeaconClientServiceBuilder) + .GetField("_discv5ProtocolBuilder", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance) + ?.SetValue(_builder, _mockDiscv5ProtocolBuilder.Object); + + var configureCalled = false; + + _builder.AddDiscoveryProtocol(builder => + { + configureCalled = true; + Assert.That(builder, Is.EqualTo(_mockDiscv5ProtocolBuilder.Object)); + }); + + Assert.That(configureCalled, Is.EqualTo(true)); + } + + [Test] + public void AddLibp2pProtocol_WhenCalled_ShouldAddLibp2pProtocolToServices() + { + var mockPeerFactoryBuilder = new Mock(); + + _builder.AddLibp2pProtocol(FactorySetup); + + _mockServicesCollection.Verify(s => + s.Add(It.Is(descriptor => + descriptor.ServiceType == typeof(PubsubRouter) && + descriptor.Lifetime == ServiceLifetime.Scoped + )), Times.Once); + + _mockServicesCollection.Verify(s => + s.Add(It.Is(descriptor => + descriptor.ServiceType == typeof(MultiplexerSettings) && + descriptor.Lifetime == ServiceLifetime.Scoped + )), Times.Once); + + _mockServicesCollection.Verify(s => + s.Add(It.Is(descriptor => + descriptor.ServiceType == typeof(IdentifyProtocolSettings) && + descriptor.Lifetime == ServiceLifetime.Scoped && + descriptor.ImplementationFactory != null + )), Times.Once); + + _mockServicesCollection.Verify(s => + s.Add(It.Is(descriptor => + descriptor.ServiceType == typeof(IPeerFactory) && + descriptor.Lifetime == ServiceLifetime.Scoped + )), Times.Once); + return; + + IPeerFactoryBuilder FactorySetup(ILibp2pPeerFactoryBuilder _) => mockPeerFactoryBuilder.Object; + } + + [Test] + public void WithSyncProtocolOptions_WhenCalled_ShouldConfigureSyncProtocolOptions() + { + var syncProtocolOptions = new SyncProtocolOptions + { + GenesisTime = 200 + }; + + _builder.WithSyncProtocolOptions(syncProtocolOptions); + + var actualOptions = typeof(BeaconClientServiceBuilder) + .GetField("_syncProtocolOptions", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance) + ?.GetValue(_builder) as SyncProtocolOptions; + + Assert.That(actualOptions, Is.Not.Null); + Assert.That(actualOptions, Is.EqualTo(syncProtocolOptions)); + Assert.That(actualOptions!.GenesisTime, Is.EqualTo(200)); + } + + [Test] + public void WithBeaconClientOptions_WhenCalled_ShouldConfigureBeaconClientOptions() + { + var beaconClientOptions = new BeaconClientOptions + { + TcpPort = 9000 + }; + + _builder.WithBeaconClientOptions(beaconClientOptions); + + var actualOptions = typeof(BeaconClientServiceBuilder) + .GetField("_beaconClientOptions", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance) + ?.GetValue(_builder) as BeaconClientOptions; + + Assert.That(actualOptions, Is.Not.Null); + Assert.That(actualOptions, Is.EqualTo(beaconClientOptions)); + Assert.That(actualOptions!.TcpPort, Is.EqualTo(9000)); + } + + [Test] + public void WithLoggerFactory_WhenCalled_ShouldConfigureLoggerFactory() + { + var loggerFactory = new Mock().Object; + + _builder.WithLoggerFactory(loggerFactory); + + var actualLoggerFactory = typeof(BeaconClientServiceBuilder) + .GetField("_loggerFactory", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance) + ?.GetValue(_builder) as ILoggerFactory; + + Assert.That(actualLoggerFactory, Is.Not.Null); + Assert.That(actualLoggerFactory, Is.EqualTo(loggerFactory)); + } + + [Test] + public void Build_WhenCalled_ShouldRunProperly() + { + _mockDiscv5ProtocolBuilder.Setup(x => x.Build()).Returns(new Mock().Object); + + typeof(BeaconClientServiceBuilder) + .GetField("_discv5ProtocolBuilder", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance) + ?.SetValue(_builder, _mockDiscv5ProtocolBuilder.Object); + + Assert.Throws(() => _builder.Build()); + + _mockDiscv5ProtocolBuilder.Verify(x => x.Build(), Times.Once); + } +} \ No newline at end of file diff --git a/test/Lantern.Beacon.Tests/BeaconClientTests.cs b/test/Lantern.Beacon.Tests/BeaconClientTests.cs index 227506b..c0f775f 100644 --- a/test/Lantern.Beacon.Tests/BeaconClientTests.cs +++ b/test/Lantern.Beacon.Tests/BeaconClientTests.cs @@ -2,18 +2,21 @@ using Lantern.Beacon.Networking.Gossip; using Lantern.Beacon.Storage; using Lantern.Beacon.Sync; +using Lantern.Beacon.Sync.Types.Ssz.Altair; +using Lantern.Beacon.Sync.Types.Ssz.Capella; +using Lantern.Beacon.Sync.Types.Ssz.Deneb; using Microsoft.Extensions.Logging; using NUnit.Framework; using Moq; using Nethermind.Libp2p.Core; - +using Nethermind.Libp2p.Protocols.Pubsub; namespace Lantern.Beacon.Tests; [TestFixture] public class BeaconClientTests { - private Mock _mockNetworkState; + private Mock _mockPeerState; private Mock _mockPeerFactoryBuilder; private Mock _mockBeaconClientManager; private Mock _mockLiteDbService; @@ -27,7 +30,7 @@ public class BeaconClientTests [SetUp] public void Setup() { - _mockNetworkState = new Mock(); + _mockPeerState = new Mock(); _mockPeerFactoryBuilder = new Mock(); _mockBeaconClientManager = new Mock(); _mockLiteDbService = new Mock(); @@ -36,34 +39,142 @@ public void Setup() _mockSyncProtocol = new Mock(); _mockGossipSubManager = new Mock(); _mockServiceProvider = new Mock(); + } - _mockBeaconClientManager.Setup(bc => bc.InitAsync(new CancellationToken())); + [Test] + public async Task InitAsync_ShouldInitializeCorrectly() + { + _mockBeaconClientManager.Setup(bc => bc.InitAsync()); _mockLoggerFactory.Setup(x => x.CreateLogger(It.IsAny())).Returns(_mockLogger.Object); _mockServiceProvider.Setup(sp => sp.GetService(typeof(ILoggerFactory))).Returns(_mockLoggerFactory.Object); - - if (_mockBeaconClientManager == null || _mockServiceProvider == null) - { - throw new InvalidOperationException("A required mock is null."); - } + _mockGossipSubManager.Setup(x => x.LightClientFinalityUpdate).Returns(new Mock().Object); + _mockGossipSubManager.Setup(x => x.LightClientOptimisticUpdate).Returns(new Mock().Object); + _beaconClient = new BeaconClient(_mockSyncProtocol.Object, _mockLiteDbService.Object, _mockPeerFactoryBuilder.Object, _mockPeerState.Object, _mockBeaconClientManager.Object, _mockGossipSubManager.Object, _mockServiceProvider.Object); + + await _beaconClient.InitAsync(); - _beaconClient = new BeaconClient(_mockSyncProtocol.Object, _mockLiteDbService.Object, _mockPeerFactoryBuilder.Object, _mockNetworkState.Object, _mockBeaconClientManager.Object, _mockGossipSubManager.Object, _mockServiceProvider.Object); + _mockLiteDbService.Verify(x => x.Init(), Times.Once); + _mockLiteDbService.Verify(x => x.Fetch(nameof(AltairLightClientStore)), Times.Once); + _mockLiteDbService.Verify(x => x.Fetch(nameof(CapellaLightClientStore)), Times.Once); + _mockLiteDbService.Verify(x => x.Fetch(nameof(DenebLightClientStore)), Times.Once); + _mockLiteDbService.Verify(x => x.Fetch(nameof(DenebLightClientFinalityUpdate)), Times.Once); + _mockLiteDbService.Verify(x => x.Fetch(nameof(DenebLightClientOptimisticUpdate)), Times.Once); + _mockSyncProtocol.Verify(x => x.Init(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + _mockPeerState.Verify(x => x.Init(It.IsAny>()), Times.Once); + _mockGossipSubManager.Verify(x => x.Init(), Times.Once); + _mockBeaconClientManager.Verify(x => x.InitAsync(), Times.Once); } - + [Test] - public async Task StartAsync_ShouldStartPeerManager() + public async Task InitAsync_ShouldThrowIfExceptionOccurs() { - var cancellationToken = new CancellationToken(); + _mockBeaconClientManager.Setup(bc => bc.InitAsync()); + _mockLoggerFactory.Setup(x => x.CreateLogger(It.IsAny())).Returns(_mockLogger.Object); + _mockServiceProvider.Setup(sp => sp.GetService(typeof(ILoggerFactory))).Returns(_mockLoggerFactory.Object); + _mockGossipSubManager.Setup(x => x.LightClientFinalityUpdate).Returns(new Mock().Object); + _mockGossipSubManager.Setup(x => x.LightClientOptimisticUpdate).Returns(new Mock().Object); + _mockGossipSubManager.Setup(x => x.Init()).Throws(new Exception()); + _beaconClient = new BeaconClient(_mockSyncProtocol.Object, _mockLiteDbService.Object, _mockPeerFactoryBuilder.Object, _mockPeerState.Object, _mockBeaconClientManager.Object, _mockGossipSubManager.Object, _mockServiceProvider.Object); - _beaconClient.StartAsync(cancellationToken); + Assert.ThrowsAsync(async () => await _beaconClient.InitAsync()); + + _mockLiteDbService.Verify(x => x.Init(), Times.Once); + _mockLiteDbService.Verify(x => x.Fetch(nameof(AltairLightClientStore)), Times.Once); + _mockLiteDbService.Verify(x => x.Fetch(nameof(CapellaLightClientStore)), Times.Once); + _mockLiteDbService.Verify(x => x.Fetch(nameof(DenebLightClientStore)), Times.Once); + _mockLiteDbService.Verify(x => x.Fetch(nameof(DenebLightClientFinalityUpdate)), Times.Once); + _mockLiteDbService.Verify(x => x.Fetch(nameof(DenebLightClientOptimisticUpdate)), Times.Once); + _mockSyncProtocol.Verify(x => x.Init(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + _mockPeerState.Verify(x => x.Init(It.IsAny>()), Times.Once); + _mockGossipSubManager.Verify(x => x.Init(), Times.Once); + _mockBeaconClientManager.Verify(x => x.InitAsync(), Times.Never); + } + + [Test] + public async Task InitAsync_ShouldNotInitializeIfTopicsAreNotSet() + { + _mockBeaconClientManager.Setup(bc => bc.InitAsync()); + _mockLoggerFactory.Setup(x => x.CreateLogger(It.IsAny())).Returns(_mockLogger.Object); + _mockServiceProvider.Setup(sp => sp.GetService(typeof(ILoggerFactory))).Returns(_mockLoggerFactory.Object); + _beaconClient = new BeaconClient(_mockSyncProtocol.Object, _mockLiteDbService.Object, _mockPeerFactoryBuilder.Object, _mockPeerState.Object, _mockBeaconClientManager.Object, _mockGossipSubManager.Object, _mockServiceProvider.Object); + + await _beaconClient.InitAsync(); + + _mockLiteDbService.Verify(x => x.Init(), Times.Once); + _mockLiteDbService.Verify(x => x.Fetch(nameof(AltairLightClientStore)), Times.Once); + _mockLiteDbService.Verify(x => x.Fetch(nameof(CapellaLightClientStore)), Times.Once); + _mockLiteDbService.Verify(x => x.Fetch(nameof(DenebLightClientStore)), Times.Once); + _mockLiteDbService.Verify(x => x.Fetch(nameof(DenebLightClientFinalityUpdate)), Times.Once); + _mockLiteDbService.Verify(x => x.Fetch(nameof(DenebLightClientOptimisticUpdate)), Times.Once); + _mockSyncProtocol.Verify(x => x.Init(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + _mockPeerState.Verify(x => x.Init(It.IsAny>()), Times.Once); + _mockGossipSubManager.Verify(x => x.Init(), Times.Once); + _mockBeaconClientManager.Verify(x => x.InitAsync(), Times.Never); + } + + [Test] + public async Task StartAsync_ShouldStartProperly() + { + _mockBeaconClientManager.Setup(bc => bc.InitAsync()); + _mockLoggerFactory.Setup(x => x.CreateLogger(It.IsAny())).Returns(_mockLogger.Object); + _mockServiceProvider.Setup(sp => sp.GetService(typeof(ILoggerFactory))).Returns(_mockLoggerFactory.Object); + _beaconClient = new BeaconClient(_mockSyncProtocol.Object, _mockLiteDbService.Object, _mockPeerFactoryBuilder.Object, _mockPeerState.Object, _mockBeaconClientManager.Object, _mockGossipSubManager.Object, _mockServiceProvider.Object); - _mockBeaconClientManager.Verify(pm => pm.StartAsync(cancellationToken), Times.Once); + await _beaconClient.StartAsync(); + + _mockGossipSubManager.Verify(x => x.StartAsync(It.IsAny()), Times.Once); + _mockBeaconClientManager.Verify(x => x.StartAsync(It.IsAny()), Times.Once); } + + [Test] + public void StartAsync_ShouldThrowIfExceptionOccurs() + { + _mockBeaconClientManager.Setup(bc => bc.InitAsync()); + _mockLoggerFactory.Setup(x => x.CreateLogger(It.IsAny())).Returns(_mockLogger.Object); + _mockServiceProvider.Setup(sp => sp.GetService(typeof(ILoggerFactory))).Returns(_mockLoggerFactory.Object); + _mockGossipSubManager.Setup(x => x.StartAsync(new CancellationToken())).Throws(new Exception()); + _beaconClient = new BeaconClient(_mockSyncProtocol.Object, _mockLiteDbService.Object, _mockPeerFactoryBuilder.Object, _mockPeerState.Object, _mockBeaconClientManager.Object, _mockGossipSubManager.Object, _mockServiceProvider.Object); + + Assert.ThrowsAsync(async () => await _beaconClient.StartAsync()); + + _mockGossipSubManager.Verify(x => x.StartAsync(It.IsAny()), Times.Once); + _mockBeaconClientManager.Verify(x => x.StartAsync(It.IsAny()), Times.Never); + } + [Test] - public async Task StopAsync_ShouldStopDiscoveryProtocol() + public async Task StopAsync_ShouldStopProperly() { + _mockBeaconClientManager.Setup(bc => bc.InitAsync()); + _mockLoggerFactory.Setup(x => x.CreateLogger(It.IsAny())).Returns(_mockLogger.Object); + _mockServiceProvider.Setup(sp => sp.GetService(typeof(ILoggerFactory))).Returns(_mockLoggerFactory.Object); + _beaconClient = new BeaconClient(_mockSyncProtocol.Object, _mockLiteDbService.Object, _mockPeerFactoryBuilder.Object, _mockPeerState.Object, _mockBeaconClientManager.Object, _mockGossipSubManager.Object, _mockServiceProvider.Object); + + _beaconClient.StartAsync(); await _beaconClient.StopAsync(); - _mockBeaconClientManager.Verify(dp => dp.StopAsync(), Times.Once); + _mockGossipSubManager.Verify(x => x.StopAsync(), Times.Once); + _mockBeaconClientManager.Verify(x => x.StopAsync(), Times.Once); + _mockLiteDbService.Verify(x => x.Dispose(), Times.Once); + Assert.That(_beaconClient.CancellationTokenSource, Is.Null); } + + [Test] + public async Task StopAsync_ShouldReturnIfCancellationTokenIsNull() + { + _mockBeaconClientManager.Setup(bc => bc.InitAsync()); + _mockLoggerFactory.Setup(x => x.CreateLogger(It.IsAny())).Returns(_mockLogger.Object); + _mockServiceProvider.Setup(sp => sp.GetService(typeof(ILoggerFactory))).Returns(_mockLoggerFactory.Object); + _beaconClient = new BeaconClient(_mockSyncProtocol.Object, _mockLiteDbService.Object, _mockPeerFactoryBuilder.Object, _mockPeerState.Object, _mockBeaconClientManager.Object, _mockGossipSubManager.Object, _mockServiceProvider.Object); + + await _beaconClient.StopAsync(); + + Assert.That(_beaconClient.CancellationTokenSource, Is.Null); + _mockGossipSubManager.Verify(x => x.StopAsync(), Times.Never); + _mockBeaconClientManager.Verify(x => x.StopAsync(), Times.Never); + _mockLiteDbService.Verify(x => x.Dispose(), Times.Never); + } + + + } \ No newline at end of file