Пример #1
0
        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}'.");
            }
        }
Пример #2
0
        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);
        }
Пример #3
0
 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);
 }
Пример #4
0
        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 _);
            }
        }
Пример #5
0
        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.");
            }
        }
Пример #6
0
        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);
        }
Пример #7
0
        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);
        }
Пример #8
0
        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);
        }
Пример #9
0
        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.");
            }
        }
Пример #10
0
        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);
        }
Пример #11
0
 public ConsumerNode?GetConsumerNode(INdmProviderPeer peer)
 => Nodes.TryGetValue(peer.NodeId, out ConsumerNode? node) ? node : null;
Пример #12
0
 public ProviderSession?GetSession(Keccak depositId, INdmProviderPeer peer) =>
 GetConsumerNode(peer)?.GetSession(depositId);
Пример #13
0
        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);
        }
Пример #14
0
        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);
            }
        }
Пример #15
0
        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);
            }
        }
Пример #16
0
 public ConsumerNode(INdmProviderPeer peer)
 {
     Peer = peer;
 }