public async Task NodeVersionInitialFailTryAgain() { // Arrange IServiceCollection testServiceCollection = TestSystem.BuildTestServiceCollection(); IBeaconNodeOApiClient beaconNodeOApiClient1 = Substitute.For <IBeaconNodeOApiClient>(); beaconNodeOApiClient1.VersionAsync(Arg.Any <CancellationToken>()).Throws(new HttpRequestException("TESTEXCEPTION")); IBeaconNodeOApiClient beaconNodeOApiClient2 = Substitute.For <IBeaconNodeOApiClient>(); beaconNodeOApiClient2.VersionAsync(Arg.Any <CancellationToken>()).Returns("TESTVERSION"); IBeaconNodeOApiClientFactory beaconNodeOApiClientFactory = Substitute.For <IBeaconNodeOApiClientFactory>(); beaconNodeOApiClientFactory.CreateClient(Arg.Any <string>()).Returns(beaconNodeOApiClient1, beaconNodeOApiClient2); testServiceCollection.AddSingleton <IBeaconNodeOApiClientFactory>(beaconNodeOApiClientFactory); ServiceProvider testServiceProvider = testServiceCollection.BuildServiceProvider(); // Act IBeaconNodeApi beaconNodeProxy = testServiceProvider.GetService <IBeaconNodeApi>(); beaconNodeProxy.ShouldBeOfType(typeof(BeaconNodeProxy)); string version1 = await beaconNodeProxy.GetNodeVersionAsync(CancellationToken.None); // Assert version1.ShouldBe("TESTVERSION"); beaconNodeOApiClientFactory.CreateClient(Arg.Any <string>()).Received(2); }
public async Task NodeVersionTwiceShouldUseSameClient() { // Arrange IServiceCollection testServiceCollection = TestSystem.BuildTestServiceCollection(); IBeaconNodeOApiClient beaconNodeOApiClient = Substitute.For <IBeaconNodeOApiClient>(); beaconNodeOApiClient.VersionAsync(Arg.Any <CancellationToken>()).Returns("TESTVERSION"); IBeaconNodeOApiClientFactory beaconNodeOApiClientFactory = Substitute.For <IBeaconNodeOApiClientFactory>(); beaconNodeOApiClientFactory.CreateClient(Arg.Any <string>()).Returns(beaconNodeOApiClient); testServiceCollection.AddSingleton <IBeaconNodeOApiClientFactory>(beaconNodeOApiClientFactory); ServiceProvider testServiceProvider = testServiceCollection.BuildServiceProvider(); // Act IBeaconNodeApi beaconNodeProxy = testServiceProvider.GetService <IBeaconNodeApi>(); beaconNodeProxy.ShouldBeOfType(typeof(BeaconNodeProxy)); string version1 = await beaconNodeProxy.GetNodeVersionAsync(CancellationToken.None); string version2 = await beaconNodeProxy.GetNodeVersionAsync(CancellationToken.None); // Assert version1.ShouldBe("TESTVERSION"); version2.ShouldBe("TESTVERSION"); beaconNodeOApiClientFactory.CreateClient(Arg.Any <string>()).Received(1); }
public async Task NodeVersionInitialFailAfterSuccessTryAgain() { // Arrange IServiceCollection testServiceCollection = TestSystem.BuildTestServiceCollection(); IBeaconNodeOApiClient beaconNodeOApiClient1 = Substitute.For <IBeaconNodeOApiClient>(); beaconNodeOApiClient1.VersionAsync(Arg.Any <CancellationToken>()).Returns( callInfo => "TESTVERSION1", callInfo => throw new HttpRequestException("TESTEXCEPTION") ); beaconNodeOApiClient1.BaseUrl.Returns("CLIENT1"); IBeaconNodeOApiClient beaconNodeOApiClient2 = Substitute.For <IBeaconNodeOApiClient>(); beaconNodeOApiClient2.VersionAsync(Arg.Any <CancellationToken>()).Returns("TESTVERSION2"); IBeaconNodeOApiClientFactory beaconNodeOApiClientFactory = Substitute.For <IBeaconNodeOApiClientFactory>(); beaconNodeOApiClientFactory.CreateClient(Arg.Any <string>()).Returns(beaconNodeOApiClient1, beaconNodeOApiClient2); testServiceCollection.AddSingleton <IBeaconNodeOApiClientFactory>(beaconNodeOApiClientFactory); ServiceProvider testServiceProvider = testServiceCollection.BuildServiceProvider(); // Act IBeaconNodeApi beaconNodeProxy = testServiceProvider.GetService <IBeaconNodeApi>(); beaconNodeProxy.ShouldBeOfType(typeof(BeaconNodeProxy)); string version1 = await beaconNodeProxy.GetNodeVersionAsync(CancellationToken.None); string version2 = await beaconNodeProxy.GetNodeVersionAsync(CancellationToken.None); // Assert version1.ShouldBe("TESTVERSION1"); version2.ShouldBe("TESTVERSION2"); beaconNodeOApiClientFactory.CreateClient(Arg.Any <string>()).Received(2); List <ICall> client1Received = beaconNodeOApiClient1.ReceivedCalls().ToList(); client1Received.Count(x => x.GetMethodInfo().Name == nameof(beaconNodeOApiClient1.VersionAsync)).ShouldBe(2); List <ICall> client2Received = beaconNodeOApiClient2.ReceivedCalls().ToList(); client2Received.Count(x => x.GetMethodInfo().Name == nameof(beaconNodeOApiClient1.VersionAsync)).ShouldBe(1); }
public async Task BasicGensisTime() { // Arrange IServiceCollection testServiceCollection = TestSystem.BuildTestServiceCollection(); IBeaconNodeOApiClient beaconNodeOApiClient = Substitute.For <IBeaconNodeOApiClient>(); beaconNodeOApiClient.TimeAsync(Arg.Any <CancellationToken>()).Returns(1_578_009_600uL); IBeaconNodeOApiClientFactory beaconNodeOApiClientFactory = Substitute.For <IBeaconNodeOApiClientFactory>(); beaconNodeOApiClientFactory.CreateClient(Arg.Any <string>()).Returns(beaconNodeOApiClient); testServiceCollection.AddSingleton <IBeaconNodeOApiClientFactory>(beaconNodeOApiClientFactory); ServiceProvider testServiceProvider = testServiceCollection.BuildServiceProvider(); // Act IBeaconNodeApi beaconNodeProxy = testServiceProvider.GetService <IBeaconNodeApi>(); beaconNodeProxy.ShouldBeOfType(typeof(BeaconNodeProxy)); ulong genesisTime = await beaconNodeProxy.GetGenesisTimeAsync(CancellationToken.None); // Assert genesisTime.ShouldBe(1578009600uL); }
// // The proxy needs to take care of this (i.e. transparent to worker) // // Not connected: (remote vs local) // // connect to beacon node (priority order) // // if not connected, wait and try next // // public async Task<string> GetNodeVersionAsync(CancellationToken cancellationToken) // { // string? result = null; // await ClientOperationWithRetry( // async (oapiClient, innerCancellationToken) => // { // result = await oapiClient.VersionAsync(innerCancellationToken).ConfigureAwait(false); // }, cancellationToken).ConfigureAwait(false); // // return result!; // } // // public Task<Syncing> GetSyncingAsync(CancellationToken cancellationToken) // { // throw new NotImplementedException(); // } // // public async Task<Core2.Containers.BeaconBlock> NewBlockAsync(Slot slot, BlsSignature randaoReveal, // CancellationToken cancellationToken) // { // ulong slotValue = (ulong) slot; // byte[] randaoRevealBytes = randaoReveal.Bytes; // // BeaconBlock? result = null; // await ClientOperationWithRetry( // async (oapiClient, innerCancellationToken) => // { // result = await oapiClient.BlockAsync(slotValue, randaoRevealBytes, innerCancellationToken) // .ConfigureAwait(false); // }, cancellationToken).ConfigureAwait(false); // // BeaconBlock oapiBeaconBlock = result!; // Core2.Containers.BeaconBlock beaconBlock = new Core2.Containers.BeaconBlock( // new Slot((ulong) oapiBeaconBlock.Slot), // new Root(Bytes.FromHexString(oapiBeaconBlock.Parent_root)), // new Root(Bytes.FromHexString(oapiBeaconBlock.State_root)), // new Core2.Containers.BeaconBlockBody( // new BlsSignature(oapiBeaconBlock.Body.Randao_reveal), // new Eth1Data( // new Root(oapiBeaconBlock.Body.Eth1_data.Deposit_root), // (ulong) oapiBeaconBlock.Body.Eth1_data.Deposit_count, // new Bytes32(oapiBeaconBlock.Body.Eth1_data.Block_hash) // ), // new Bytes32(oapiBeaconBlock.Body.Graffiti), // oapiBeaconBlock.Body.Proposer_slashings.Select(x => new ProposerSlashing( // new ValidatorIndex((ulong) x.Proposer_index), // new SignedBeaconBlockHeader(MapBeaconBlockHeader(x.Header_1), BlsSignature.Zero), // new SignedBeaconBlockHeader(MapBeaconBlockHeader(x.Header_2), BlsSignature.Zero) // )), // oapiBeaconBlock.Body.Attester_slashings.Select(x => new AttesterSlashing( // MapIndexedAttestation(x.Attestation_1), // MapIndexedAttestation(x.Attestation_2) // )), // oapiBeaconBlock.Body.Attestations.Select(x => // new Core2.Containers.Attestation( // new BitArray(x.Aggregation_bits), // MapAttestationData(x.Data), // new BlsSignature(x.Signature) // ) // ), // oapiBeaconBlock.Body.Deposits.Select(x => // new Deposit( // x.Proof.Select(y => new Bytes32(y)), // new DepositData( // new BlsPublicKey(x.Data.Pubkey), // new Bytes32(x.Data.Withdrawal_credentials), // new Gwei((ulong) x.Data.Amount), // new BlsSignature(x.Data.Signature) // ) // ) // ), // oapiBeaconBlock.Body.Voluntary_exits.Select(x => // new SignedVoluntaryExit( // new VoluntaryExit( // new Epoch((ulong) x.Epoch), // new ValidatorIndex((ulong) x.Validator_index) // ), // BlsSignature.Zero // ) // ) // ) // ); // // return beaconBlock; // } // // public async Task<bool> PublishBlockAsync(SignedBeaconBlock signedBlock, CancellationToken cancellationToken) // { // var block = signedBlock.Message; // BeaconBlock data = new BeaconBlock() // { // Slot = block.Slot, // Parent_root = block.ParentRoot.ToString(), // State_root = block.StateRoot.ToString(), // Body = new BeaconBlockBody() // { // Randao_reveal = block.Body!.RandaoReveal.AsSpan().ToArray(), // Eth1_data = new Eth1_data() // { // Block_hash = block.Body.Eth1Data.BlockHash.AsSpan().ToArray(), // Deposit_count = block.Body.Eth1Data.DepositCount, // Deposit_root = block.Body.Eth1Data.DepositRoot.AsSpan().ToArray() // }, // Graffiti = block.Body.Graffiti.AsSpan().ToArray(), // Proposer_slashings = block.Body.ProposerSlashings.Select(x => new Proposer_slashings() // { // Header_1 = MapBeaconBlockHeader(x.SignedHeader1.Message), // Header_2 = MapBeaconBlockHeader(x.SignedHeader2.Message), // Proposer_index = x.ProposerIndex // }).ToList(), // Attester_slashings = block.Body.AttesterSlashings.Select(x => new Attester_slashings() // { // Attestation_1 = MapIndexedAttestation(x.Attestation1), // Attestation_2 = MapIndexedAttestation(x.Attestation2) // }).ToList(), // Attestations = block.Body.Attestations.Select(x => new Attestations() // { // Signature = x.Signature.Bytes, // Aggregation_bits = x.AggregationBits.Cast<byte>().ToArray(), // Custody_bits = new byte[0], // Data = MapAttestationData(x.Data) // }).ToList(), // Voluntary_exits = block.Body.VoluntaryExits.Select(x => new Voluntary_exits() // { // Validator_index = x.Message.ValidatorIndex, // Epoch = x.Message.Epoch, // Signature = x.Signature.Bytes // }).ToList(), // Deposits = block.Body.Deposits.Select((x, index) => new Deposits() // { // Index = (ulong) index, // Proof = x.Proof.Select(y => y.AsSpan().ToArray()).ToList(), // Data = new Data() // { // Amount = x.Data.Amount, // Pubkey = x.Data.PublicKey.Bytes, // Signature = x.Data.Signature.Bytes, // Withdrawal_credentials = x.Data.WithdrawalCredentials.AsSpan().ToArray() // } // }).ToList(), // } // }; // // await ClientOperationWithRetry( // async (oapiClient, innerCancellationToken) => // { // await oapiClient.Block2Async(data, signedBlock.Signature, innerCancellationToken) // .ConfigureAwait(false); // }, cancellationToken).ConfigureAwait(false); // // // TODO: Parse 202 result separate from 200 result // // return true; // } // // public async IAsyncEnumerable<Core2.Api.ValidatorDuty> ValidatorDutiesAsync( // IEnumerable<BlsPublicKey> validatorPublicKeys, // Epoch epoch, [EnumeratorCancellation] CancellationToken cancellationToken) // { // IEnumerable<byte[]> validator_pubkeys = validatorPublicKeys.Select(x => x.Bytes); // ulong? epochValue = (epoch != Epoch.None) ? (ulong?) epoch : null; // // ICollection<ValidatorDuty>? result = null; // await ClientOperationWithRetry( // async (oapiClient, innerCancellationToken) => // { // result = await oapiClient.DutiesAsync(validator_pubkeys, epochValue, innerCancellationToken) // .ConfigureAwait(false); // }, cancellationToken).ConfigureAwait(false); // // foreach (var value in result!) // { // var validatorPublicKey = new BlsPublicKey(value.Validator_pubkey); // var proposalSlot = value.Block_proposal_slot.HasValue // ? new Slot(value.Block_proposal_slot.Value) // : Slot.None; // var validatorDuty = new Core2.Api.ValidatorDuty(validatorPublicKey, new Slot(value.Attestation_slot), // new Shard(value.Attestation_shard), proposalSlot); // yield return validatorDuty; // } // } private async Task ClientOperationWithRetry( Func <IBeaconNodeOApiClient, CancellationToken, Task> clientOperation, CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { IBeaconNodeOApiClient?localClient = _oapiClient; if (!(localClient is null)) { try { await clientOperation(localClient, cancellationToken).ConfigureAwait(false); // exit loop and complete on success break; } catch (HttpRequestException ex) { // Only null out if the same client is still there (i.e. no one else has replaced) IBeaconNodeOApiClient?exchangeResult = Interlocked.CompareExchange(ref _oapiClient, null, localClient); if (exchangeResult == localClient) { if (_logger.IsWarn()) { Log.NodeConnectionFailed(_logger, localClient.BaseUrl, ex); } } } } // take turns trying the first connection await _connectionAttemptSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try { // this routine's turn to try and connect (if no one else has) if (_oapiClient is null) { // create new client _connectionIndex++; BeaconNodeConnection beaconNodeConnection = _beaconNodeConnectionOptions.CurrentValue; if (_connectionIndex >= beaconNodeConnection.RemoteUrls.Length) { _connectionIndex = 0; if (_logger.IsWarn()) { Log.AllNodeConnectionsFailing(_logger, beaconNodeConnection.RemoteUrls.Length, beaconNodeConnection.ConnectionFailureLoopMillisecondsDelay, null); } await Task.Delay(beaconNodeConnection.ConnectionFailureLoopMillisecondsDelay, cancellationToken).ConfigureAwait(false); } string baseUrl = beaconNodeConnection.RemoteUrls[_connectionIndex]; if (_logger.IsDebug()) { LogDebug.AttemptingConnectionToNode(_logger, baseUrl, _connectionIndex, null); } IBeaconNodeOApiClient newClient = _oapiClientFactory.CreateClient(baseUrl); // check if it works await clientOperation(newClient, cancellationToken).ConfigureAwait(false); // success! set the client, and if not the first, set the connection index to restart from first if (_logger.IsInfo()) { Log.NodeConnectionSuccess(_logger, baseUrl, _connectionIndex, null); } _oapiClient = newClient; if (_connectionIndex > 0) { _connectionIndex = -1; } // exit loop and complete on success break; } } catch (HttpRequestException) { // Continue } finally { _connectionAttemptSemaphore.Release(); } } }