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}]."); } }
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); } }