public DataDeliveryReceiptReportItem(Keccak id, uint number, Keccak sessionId, PublicKey nodeId,
                                      DataDeliveryReceiptRequest request, DataDeliveryReceipt receipt, ulong timestamp, bool isMerged,
                                      bool isClaimed)
 {
     Id        = id;
     Number    = number;
     SessionId = sessionId;
     NodeId    = nodeId;
     Request   = request;
     Receipt   = receipt;
     Timestamp = timestamp;
     IsMerged  = isMerged;
     IsClaimed = isClaimed;
 }
예제 #2
0
        public async Task given_invalid_consumer_address_delivery_receipt_should_not_be_processed()
        {
            _signer.RecoverPublicKey(Arg.Any <Signature>(), Arg.Any <Keccak>()).ReturnsForAnyArgs(TestItem.PublicKeyB);
            _peer.ConsumerAddress.Returns(TestItem.AddressC);

            var currentReceiptRequestUnitsRange = new UnitsRange(0, 9);
            var receiptRequest  = new DataDeliveryReceiptRequest(1, Keccak.Zero, currentReceiptRequestUnitsRange);
            var deliveryReceipt = new DataDeliveryReceipt(StatusCodes.Ok, 0, 0, new Signature(new byte[65]));

            var wasProcessed = await _processor.TryProcessAsync(_session, _publicKey.Address, _peer, receiptRequest,
                                                                deliveryReceipt);

            wasProcessed.Should().BeFalse();
            _session.DataAvailability.Should().Be(DataAvailability.DataDeliveryReceiptInvalid);
        }
        public DataDeliveryReceiptDetails Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None)
        {
            rlpStream.ReadSequenceLength();
            Keccak    id                       = rlpStream.DecodeKeccak();
            Keccak    sessionId                = rlpStream.DecodeKeccak();
            Keccak    dataAssetId              = rlpStream.DecodeKeccak();
            PublicKey consumerNodeId           = new PublicKey(rlpStream.DecodeByteArray());
            DataDeliveryReceiptRequest request = Serialization.Rlp.Rlp.Decode <DataDeliveryReceiptRequest>(rlpStream);
            DataDeliveryReceipt        receipt = Serialization.Rlp.Rlp.Decode <DataDeliveryReceipt>(rlpStream);
            ulong timestamp                    = rlpStream.DecodeUlong();
            bool  isClaimed                    = rlpStream.DecodeBool();

            return(new DataDeliveryReceiptDetails(id, sessionId, dataAssetId, consumerNodeId, request, receipt,
                                                  timestamp, isClaimed));
        }
예제 #4
0
        private void AddReciptsToMerge(TestConsumer consumer, IDepositNodesHandler depositHandler)
        {
            DataDeliveryReceiptRequest request = new DataDeliveryReceiptRequest(1, consumer.DepositId, new UnitsRange(0, 5), false, new List <DataDeliveryReceiptToMerge> {
                new DataDeliveryReceiptToMerge(new UnitsRange(0, 1), new Signature(1, 2, 37))
            });
            DataDeliveryReceipt        receipt        = new DataDeliveryReceipt(StatusCodes.Ok, 50, 0, new Signature(1, 2, 37));
            DataDeliveryReceiptDetails receiptDetails = new DataDeliveryReceiptDetails(Keccak.OfAnEmptyString, consumer.Sessions.First().Id, consumer.DataAsset.Id, null, request, receipt, 10, true);

            depositHandler.AddReceipt(receiptDetails);

            DataDeliveryReceiptRequest request2        = new DataDeliveryReceiptRequest(1, consumer.DepositId, new UnitsRange(6, 49));
            DataDeliveryReceipt        receipt2        = new DataDeliveryReceipt(StatusCodes.Ok, 50, 0, new Signature(1, 2, 37));
            DataDeliveryReceiptDetails receiptDetails2 = new DataDeliveryReceiptDetails(Keccak.OfAnEmptyString, consumer.Sessions.First().Id, consumer.DataAsset.Id, null, request2, receipt2, 10, false);

            depositHandler.AddReceipt(receiptDetails2);
        }
예제 #5
0
 public DataDeliveryReceiptForRpc(DataDeliveryReceipt receipt)
 {
     StatusCode    = receipt.StatusCode.ToString().ToLowerInvariant();
     ConsumedUnits = receipt.ConsumedUnits;
     UnpaidUnits   = receipt.UnpaidUnits;
 }
예제 #6
0
 public DataDeliveryReceiptMessage(Keccak depositId, DataDeliveryReceipt receipt)
 {
     DepositId = depositId;
     Receipt   = receipt;
 }
예제 #7
0
        public async Task SendAsync(DataDeliveryReceiptRequest request, int fetchSessionRetries = 3,
                                    int fetchSessionRetryDelayMilliseconds = 3000)
        {
            var depositId = request.DepositId;

            var(deposit, session) = await TryGetDepositAndSessionAsync(depositId,
                                                                       fetchSessionRetries, fetchSessionRetryDelayMilliseconds);

            if (deposit is null || session is null)
            {
                return;
            }

            var providerAddress = deposit.DataAsset.Provider.Address;
            var providerPeer    = _providerService.GetPeer(providerAddress);

            if (providerPeer is null)
            {
                if (_logger.IsWarn)
                {
                    _logger.Warn($"Provider: '{providerAddress}' was not found.");
                }

                return;
            }

            var receiptId = Keccak.Compute(Rlp.Encode(Rlp.Encode(depositId), Rlp.Encode(request.Number),
                                                      Rlp.Encode(_timestamper.EpochSeconds)));

            if (!_receiptRequestValidator.IsValid(request, session.UnpaidUnits, session.ConsumedUnits,
                                                  deposit.Deposit.Units))
            {
                if (_logger.IsWarn)
                {
                    _logger.Warn($"Provider: '{providerPeer.NodeId}' sent an invalid data delivery receipt request.");
                }
                var receipt = new DataDeliveryReceipt(StatusCodes.InvalidReceiptRequestRange,
                                                      session.ConsumedUnits, session.UnpaidUnits, new Signature(1, 1, 27));
                await _receiptRepository.AddAsync(new DataDeliveryReceiptDetails(receiptId, session.Id,
                                                                                 session.DataAssetId, _nodePublicKey, request, receipt, _timestamper.EpochSeconds, false));

                await _sessionRepository.UpdateAsync(session);

                providerPeer.SendDataDeliveryReceipt(depositId, receipt);
                return;
            }

            var abiHash = _abiEncoder.Encode(AbiEncodingStyle.Packed, _dataDeliveryReceiptAbiSig,
                                             depositId.Bytes, new[] { request.UnitsRange.From, request.UnitsRange.To });
            var receiptHash      = Keccak.Compute(abiHash);
            var signature        = _wallet.Sign(receiptHash, deposit.Consumer);
            var recoveredAddress = _ecdsa.RecoverPublicKey(signature, receiptHash)?.Address;

            if (deposit.Consumer != recoveredAddress)
            {
                if (_logger.IsError)
                {
                    _logger.Error($"Signature failure when signing the receipt from provider: '{providerPeer.NodeId}', invalid recovered address.");
                }
                var receipt = new DataDeliveryReceipt(StatusCodes.InvalidReceiptAddress,
                                                      session.ConsumedUnits, session.UnpaidUnits, new Signature(1, 1, 27));
                await _receiptRepository.AddAsync(new DataDeliveryReceiptDetails(receiptId, session.Id,
                                                                                 session.DataAssetId, _nodePublicKey, request, receipt, _timestamper.EpochSeconds, false));

                await _sessionRepository.UpdateAsync(session);

                providerPeer.SendDataDeliveryReceipt(depositId, receipt);
                return;
            }

            if (_logger.IsInfo)
            {
                _logger.Info($"Provider: '{providerPeer.NodeId}' sent a valid data delivery receipt request.");
            }
            if (request.ReceiptsToMerge.Any())
            {
                if (_logger.IsInfo)
                {
                    _logger.Info($"Processing a merged receipt request for deposit: {session.DepositId}, session: '{session.Id} - units will not be updated.");
                }
            }
            else
            {
                var paidUnits   = request.UnitsRange.To - request.UnitsRange.From + 1;
                var unpaidUnits = session.UnpaidUnits > paidUnits ? session.UnpaidUnits - paidUnits : 0;
                session.SetUnpaidUnits(unpaidUnits);
                session.AddPaidUnits(paidUnits);
                if (request.IsSettlement)
                {
                    session.SetPaidUnits(paidUnits);
                    session.SettleUnits(paidUnits);
                    if (_logger.IsInfo)
                    {
                        _logger.Info($"Settled {paidUnits} units for deposit: '{session.DepositId}', session: '{session.Id}'.");
                    }
                }

                await _sessionRepository.UpdateAsync(session);
            }

            if (_logger.IsInfo)
            {
                _logger.Info($"Sending data delivery receipt for deposit: '{depositId}', range: [{request.UnitsRange.From}, {request.UnitsRange.To}].");
            }
            var deliveryReceipt = new DataDeliveryReceipt(StatusCodes.Ok, session.ConsumedUnits,
                                                          session.UnpaidUnits, signature);
            await _receiptRepository.AddAsync(new DataDeliveryReceiptDetails(receiptId, session.Id,
                                                                             session.DataAssetId, _nodePublicKey, request, deliveryReceipt, _timestamper.EpochSeconds, false));

            providerPeer.SendDataDeliveryReceipt(depositId, deliveryReceipt);
            if (_logger.IsInfo)
            {
                _logger.Info($"Sent data delivery receipt for deposit: '{depositId}', range: [{request.UnitsRange.From}, {request.UnitsRange.To}].");
            }
        }
예제 #8
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);
        }
예제 #9
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);
            }
        }