public void SetSession(ProviderSession session, INdmProviderPeer peer) { if (_logger.IsInfo) { _logger.Info($"Setting an active session: '{session.Id}' for node: '{peer.NodeId}', address: '{peer.ConsumerAddress}', deposit: '{session.DepositId}'."); } var node = GetConsumerNode(peer); if (node is null) { if (_logger.IsInfo) { _logger.Info($"Couldn't set an active session: '{session.Id}' for node: '{peer.NodeId}', deposit: '{session.DepositId}' - consumer node was not found."); } return; } node.AddSession(session); var depositNodes = DepositNodes.AddOrUpdate(session.DepositId, _ => new ConcurrentDictionary <PublicKey, ConsumerNode>(), (_, nodes) => nodes); depositNodes.TryRemove(peer.NodeId, out _); depositNodes.TryAdd(peer.NodeId, node); if (_logger.IsInfo) { _logger.Info($"Set an active session: '{session.Id}' for node: '{peer.NodeId}', deposit: '{session.DepositId}'."); } }
public async Task HandleUnpaidUnitsAsync(Keccak depositId, INdmProviderPeer peer) { IDepositNodesHandler?deposit = GetDepositNodesHandler(depositId); if (deposit == null) { throw new InvalidDataException($"Cannot resolve deposit for deposit ID {depositId}"); } ProviderSession?currentSession = _sessionManager.GetSession(depositId, peer); if (currentSession is null) { if (_logger.IsError) { _logger.Error($"Cannot handle unpaid units due to missing session for deposit {depositId}"); } throw new InvalidDataException("Cannot handle unpaid units due to missing session for deposit {depositId}"); } if (currentSession.UnpaidUnits > 0) { if (_logger.IsInfo) { _logger.Info($"Consumer: '{peer.NodeId}', deposit: '{depositId}' has {currentSession.UnpaidUnits} unpaid units from current session."); } deposit.AddUnpaidUnits(currentSession.UnpaidUnits); await TryHandleReceiptAsync(currentSession, deposit.Consumer, peer); } ProviderSession?previousSession = await _sessionRepository.GetPreviousAsync(currentSession); if (previousSession?.UnpaidUnits > 0) { if (_logger.IsInfo) { _logger.Info($"Consumer: '{peer.ConsumerAddress}', deposit: '{depositId}' has {previousSession.UnpaidUnits} unpaid units from previous session: '{previousSession.Id}'."); } deposit.AddUnpaidUnits(previousSession.UnpaidUnits); await TryHandleReceiptAsync(currentSession, deposit.Consumer, peer, previousSession); } if (_logger.IsTrace) { _logger.Trace($"Deposit: '{depositId}' has {deposit.UnpaidUnits} unpaid units, unmerged: {deposit.UnmergedUnits}, unclaimed: {deposit.UnclaimedUnits}."); } if (deposit.ConsumedAll || await _receiptsPolicies.CanMergeReceipts(deposit.UnmergedUnits, deposit.UnitPrice)) { await TryMergeReceiptsAsync(deposit); } await TryClaimPaymentAsync(deposit); }
public void SetUp() { _sessionRepository = Substitute.For <IProviderSessionRepository>(); _encoder = Substitute.For <IAbiEncoder>(); _signer = Substitute.For <IEcdsa>(); _peer = Substitute.For <INdmProviderPeer>(); _publicKey = new PublicKey(new byte[64]); _session = new ProviderSession(Keccak.Zero, Keccak.Zero, Keccak.Zero, _publicKey.Address, _publicKey, TestItem.AddressB, TestItem.PublicKeyB, 0, 0); _session.SetDataAvailability(DataAvailability.Available); _session.Start(0); _processor = new ReceiptProcessor(_sessionRepository, _encoder, _signer, LimboLogs.Instance); }
private void TryRemoveDepositNode(Keccak depositId, INdmProviderPeer peer) { if (!DepositNodes.TryGetValue(depositId, out var nodes)) { return; } nodes.TryRemove(peer.NodeId, out _); if (nodes.Count == 0) { DepositNodes.TryRemove(depositId, out _); } }
public void AddPeer(INdmProviderPeer peer) { if (Nodes.TryAdd(peer.NodeId, new ConsumerNode(peer))) { if (_logger.IsInfo) { _logger.Info($"Added node: '{peer.NodeId}' for address: '{peer.ConsumerAddress}'."); } return; } if (_logger.IsError) { _logger.Error($"Node: '{peer.NodeId}' for address: '{peer.ConsumerAddress}' couldn't be added."); } }
public async Task FinishSessionsAsync(INdmProviderPeer peer, bool removePeer = true) { if (!Nodes.TryGetValue(peer.NodeId, out var node)) { if (_logger.IsInfo) { _logger.Info($"No sessions to be finished found for node: '{peer.NodeId}'."); } return; } if (_logger.IsInfo) { _logger.Info($"Finishing session(s) for node: '{peer.NodeId}'."); } var timestamp = _timestamper.UnixTime.Seconds; foreach (var session in node.Sessions) { if (_logger.IsInfo) { _logger.Info($"Finishing a session: '{session.Id}' for deposit: '{session.DepositId}', node: '{peer.NodeId}'."); } session.Finish(SessionState.ConsumerDisconnected, timestamp); await _sessionRepository.UpdateAsync(session); node.RemoveSession(session.DepositId); if (removePeer) { TryRemoveDepositNode(session.DepositId, peer); } if (_logger.IsInfo) { _logger.Info($"Finished a session: '{session.Id}' for deposit: '{session.DepositId}', node: '{peer.NodeId}', timestamp: {timestamp}."); } } if (!removePeer) { return; } TryRemovePeer(node, peer); }
public async Task FinishSessionAsync(Keccak depositId, INdmProviderPeer peer, bool removePeer = true) { var session = GetSession(depositId, peer); if (session is null) { if (_logger.IsInfo) { _logger.Info($"Session for deposit: '{depositId}', node: '{peer.NodeId}' was not found."); } return; } if (_logger.IsInfo) { _logger.Info($"Finishing a session for deposit: '{depositId}', node: '{peer.NodeId}'."); } var timestamp = _timestamper.UnixTime.Seconds; session.Finish(SessionState.FinishedByConsumer, timestamp); await _sessionRepository.UpdateAsync(session); peer.SendSessionFinished(session); ConsumerNode?node = GetConsumerNode(peer); if (node == null) { throw new InvalidDataException($"COuld not find consumer node for peer {peer.NodeId} with consumer address {peer.ConsumerAddress}"); } node.RemoveSession(session.DepositId); if (_logger.IsInfo) { _logger.Info($"Finished a session: '{session.Id}' for deposit: '{session.DepositId}', node: '{peer.NodeId}', timestamp: {timestamp}."); } if (!removePeer) { return; } TryRemoveDepositNode(session.DepositId, peer); TryRemovePeer(node, peer); }
public void can_add_consumer() { testConsumer = TestConsumer.ForDeposit(Keccak.Zero) .WithNode(1).AddSession().WithUnpaidUnits(10) .And.Build(); ConsumerNode consumerNode = testConsumer.Node(1).Node; INdmProviderPeer consumerPeer = consumerNode.Peer; ConsumerNode[] pre = sessionManager.GetConsumerNodes().ToArray(); bool consumerAlreadyAdded = pre.Contains(consumerNode); Assert.IsTrue(!consumerAlreadyAdded); providerService.AddConsumerPeer(consumerPeer); var addedConsumer = sessionManager.GetConsumerNodes().Where(node => node.Peer.Equals(consumerPeer)); Assert.IsTrue(addedConsumer != null); }
private void TryRemovePeer(ConsumerNode consumerNode, INdmProviderPeer peer) { if (consumerNode.HasSessions) { if (_logger.IsInfo) { _logger.Info($"Connected node: '{peer.NodeId}' has active sessions and will not be removed."); } return; } if (Nodes.TryRemove(peer.NodeId, out _)) { return; } if (_logger.IsError) { _logger.Error($"Node: '{peer.NodeId}' for address: '{peer.ProviderAddress}' couldn't be removed."); } }
private async Task <DataDeliveryReceiptDetails?> TryHandleReceiptAsync(ProviderSession session, Address consumer, INdmProviderPeer peer, ProviderSession?previousSession = null) { Keccak depositId = session.DepositId; try { bool isSettlement = !(previousSession is null); uint unpaidSessionUnits = previousSession?.UnpaidUnits ?? session.UnpaidUnits; DataDeliveryReceiptRequest request = CreateRequest(depositId, session.Id, unpaidSessionUnits, isSettlement); return(await TryHandleReceiptAsync(session, consumer, peer, request)); } catch (Exception ex) { if (_logger.IsError) { _logger.Error($"There was an error when handling the receipts for deposit: '{depositId}', node: '{peer.NodeId}'.{ex}", ex); } } return(null); }
public ConsumerNode?GetConsumerNode(INdmProviderPeer peer) => Nodes.TryGetValue(peer.NodeId, out ConsumerNode? node) ? node : null;
public ProviderSession?GetSession(Keccak depositId, INdmProviderPeer peer) => GetConsumerNode(peer)?.GetSession(depositId);
public async Task <bool> TryProcessAsync(ProviderSession session, Address consumer, INdmProviderPeer peer, DataDeliveryReceiptRequest receiptRequest, DataDeliveryReceipt deliveryReceipt) { var depositId = session.DepositId; var unitsRange = receiptRequest.UnitsRange; var abiHash = _abiEncoder.Encode(AbiEncodingStyle.Packed, _dataDeliveryReceiptAbiSig, receiptRequest.DepositId.Bytes, new[] { unitsRange.From, unitsRange.To }); var address = _ecdsa.RecoverPublicKey(deliveryReceipt.Signature, Keccak.Compute(abiHash)).Address; if (!consumer.Equals(address)) { if (_logger.IsWarn) { _logger.Warn($"Recovered an invalid address: '{address}' (should be: '{consumer}') for delivery receipt for deposit: '{depositId}', consumer: '{session.ConsumerAddress}', session: '{session.Id}'."); } session.SetDataAvailability(DataAvailability.DataDeliveryReceiptInvalid); await _sessionRepository.UpdateAsync(session); peer.SendDataAvailability(depositId, DataAvailability.DataDeliveryReceiptInvalid); return(false); } var paidUnits = unitsRange.To - unitsRange.From + 1; if (_logger.IsInfo) { _logger.Info($"Consumer: '{consumer}' has provided a valid receipt for deposit: '{receiptRequest.DepositId}', range: [{unitsRange.From}, {unitsRange.To}], paid units: {paidUnits}"); } if (receiptRequest.ReceiptsToMerge.Any()) { if (_logger.IsInfo) { _logger.Info($"Processing a merged receipt request for consumer: {session.ConsumerAddress}, session: '{session.Id} - units will not be updated."); } } else { if (_logger.IsInfo) { _logger.Info($"Processing a receipt request for deposit: '{receiptRequest.DepositId}', consumer: {session.ConsumerAddress}, session: '{session.Id} - units will be updated."); } var unpaidUnits = session.UnpaidUnits > paidUnits ? session.UnpaidUnits - paidUnits : 0; session.SetUnpaidUnits(unpaidUnits); session.AddPaidUnits(paidUnits); if (receiptRequest.IsSettlement) { session.SetPaidUnits(paidUnits); session.SettleUnits(paidUnits); if (_logger.IsInfo) { _logger.Info($"Settled {paidUnits} units for deposit: '{receiptRequest.DepositId}', consumer: {session.ConsumerAddress}, session: '{session.Id}'."); } } await _sessionRepository.UpdateAsync(session); } var dataAvailability = session.DataAvailability; if (dataAvailability == DataAvailability.DataDeliveryReceiptInvalid || dataAvailability == DataAvailability.DataDeliveryReceiptNotProvided) { session.SetDataAvailability(DataAvailability.Available); await _sessionRepository.UpdateAsync(session); if (_logger.IsInfo) { _logger.Info($"Updated previously set data availability: '{dataAvailability}' -> '{DataAvailability.Available}', consumer: {session.ConsumerAddress}, session: '{session.Id}'."); } } return(true); }
private async Task <(DataDeliveryReceipt, RequestReceiptStatus)> TryHandleDeliveryReceipt(IDepositNodesHandler deposit, Address consumer, ProviderSession session, DataDeliveryReceiptRequest request, INdmProviderPeer peer, bool isRetry = false) { while (true) { Keccak depositId = session.DepositId; DataDeliveryReceipt receipt = await peer.SendRequestDataDeliveryReceiptAsync(request, CancellationToken.None); if (_logger.IsInfo) { _logger.Info($"Received a delivery receipt for deposit: '{depositId}' from node: '{peer.NodeId}', status code: '{receipt.StatusCode}'."); } if (isRetry && receipt.StatusCode != StatusCodes.Ok) { if (_logger.IsWarn) { _logger.Warn($"Request receipt retry failed for: '{depositId}' from node: '{peer.NodeId}', status code: '{receipt.StatusCode}'."); } return(receipt, RequestReceiptStatus.Invalid); } switch (receipt.StatusCode) { case StatusCodes.Ok: if (await _receiptProcessor.TryProcessAsync(session, consumer, peer, request, receipt)) { return(receipt, RequestReceiptStatus.Ok); } return(receipt, RequestReceiptStatus.Invalid); case StatusCodes.InvalidReceiptRequestRange: { if (_logger.IsInfo) { _logger.Info($"Consumer for deposit: '{depositId}' from node: '{peer.NodeId}' claims {receipt.ConsumedUnits} consumed and {receipt.UnpaidUnits} unpaid units."); } UnitsRange range = request.UnitsRange; uint requestedUnits = range.To - range.From + 1; if (requestedUnits <= receipt.UnpaidUnits) { if (_logger.IsWarn) { _logger.Warn($"Consumer for deposit: '{depositId}' from node: '{peer.NodeId}' claimed an invalid range (while it was actually valid)."); } break; } uint graceUnits = requestedUnits - receipt.UnpaidUnits; uint totalGraceUnits = graceUnits + deposit.GraceUnits; bool hasReachedThreshold = await _receiptsPolicies.CanClaimPayment(totalGraceUnits, deposit.UnitPrice); if (hasReachedThreshold) { peer.SendGraceUnitsExceeded(depositId, deposit.ConsumedUnits, totalGraceUnits); if (_logger.IsWarn) { _logger.Warn($"Consumer for deposit: '{depositId}' from node: '{peer.NodeId}' claimed too many unpaid units, grace units exceeded ({totalGraceUnits})."); } return(receipt, RequestReceiptStatus.GraceUnitsExceeded); } if (_logger.IsInfo) { _logger.Info($"Unpaid units difference is: {graceUnits} ({requestedUnits} - {receipt.UnpaidUnits}). Lowering units amount for deposit: '{depositId}'."); } deposit.SubtractUnpaidUnits(graceUnits); deposit.SubtractUnmergedUnits(graceUnits); deposit.SubtractUnclaimedUnits(graceUnits); deposit.AddGraceUnits(graceUnits); session.SubtractUnpaidUnits(graceUnits); session.AddGraceUnits(graceUnits); if (range.To < graceUnits) { peer.SendGraceUnitsExceeded(depositId, deposit.ConsumedUnits, totalGraceUnits); if (_logger.IsWarn) { _logger.Warn($"Cannot request a receipt for deposit: '{depositId}' - grace units amount is greater than the receipt range ({graceUnits} > {range.To})."); } return(receipt, RequestReceiptStatus.GraceUnitsExceeded); } uint updatedRangeTo = range.To - graceUnits; if (range.From > updatedRangeTo) { if (_logger.IsWarn) { _logger.Warn($"Invalid updated range [{range.From}, {updatedRangeTo}] for: '{depositId}' - receipt request will not be send."); } return(receipt, RequestReceiptStatus.Invalid); } if (_logger.IsInfo) { _logger.Info($"Grace units for deposit: '{depositId}' is: {deposit.GraceUnits} (added {graceUnits})."); } UnitsRange updatedRange = new UnitsRange(range.From, updatedRangeTo); if (_logger.IsInfo) { _logger.Info($"Updated range for deposit: '{depositId}' [{updatedRange.From}, {updatedRange.To}]. Requesting receipt once again."); } request = request.WithRange(updatedRange, deposit.GetNextReceiptRequestNumber()); isRetry = true; continue; } case StatusCodes.InvalidReceiptAddress: { break; } case StatusCodes.Error: { break; } } return(receipt, RequestReceiptStatus.Invalid); } }
private async Task <DataDeliveryReceiptDetails?> TryHandleReceiptAsync(ProviderSession session, Address consumer, INdmProviderPeer peer, DataDeliveryReceiptRequest request) { Keccak depositId = session.DepositId; IDepositNodesHandler?deposit = GetDepositNodesHandler(depositId); if (deposit == null) { throw new InvalidDataException($"Cannot resolve deposit handle for deposit ID: {depositId}"); } try { UnitsRange unitsRange = request.UnitsRange; if (_logger.IsInfo) { _logger.Info($"Sending data delivery receipt request ({request.Number}), deposit: '{depositId}', session: '{session.Id}', range: [{unitsRange.From}, {unitsRange.To}]."); } if (request.ReceiptsToMerge.Any()) { if (_logger.IsInfo) { _logger.Info($"Receipts to merge for deposit: '{depositId}': {string.Join(", ", request.ReceiptsToMerge.Select(r => $"[{r.UnitsRange.From}, {r.UnitsRange.To}]"))}"); } } (DataDeliveryReceipt receipt, RequestReceiptStatus status) = await TryHandleDeliveryReceipt(deposit, consumer, session, request, peer); Keccak id = Keccak.Compute(Rlp.Encode(Rlp.Encode(depositId), Rlp.Encode(request.Number)).Bytes); DataDeliveryReceiptDetails receiptDetails = new DataDeliveryReceiptDetails(id, session.Id, session.DataAssetId, peer.NodeId, request, receipt, _timestamper.UnixTime.Seconds, false); await _receiptRepository.AddAsync(receiptDetails); if (status != RequestReceiptStatus.Ok) { return(null); } UnitsRange range = request.UnitsRange; uint claimedUnits = range.To - range.From + 1; deposit.AddReceipt(receiptDetails); if (!request.ReceiptsToMerge.Any()) { deposit.SubtractUnpaidUnits(claimedUnits); } if (_logger.IsInfo) { _logger.Info($"Successfully processed receipt ({claimedUnits} units) for deposit: '{depositId}' with node: '{peer.NodeId}'."); } return(receiptDetails); } catch (Exception ex) { if (_logger.IsWarn) { _logger.Warn($"There was an error when processing a receipt for deposit: '{depositId}'. {ex}"); } session.SetDataAvailability(DataAvailability.DataDeliveryReceiptNotProvided); await _sessionRepository.UpdateAsync(session); peer.SendDataAvailability(depositId, DataAvailability.DataDeliveryReceiptInvalid); return(null); } }
public ConsumerNode(INdmProviderPeer peer) { Peer = peer; }